feat(guides): scaffold new guide form, hook, and update schemas
- Add useNewGuide hook with state and handlers for guide, frames, and steps - Add title, description, and file upload inputs to NewGuide - Add FileUpload component and supporting hooks - Add guideId to FrameSchema, remove steps and order - Remove content field and frames array from GuideSchema - Remove order field from StepsSchema
This commit is contained in:
parent
fc63290806
commit
8a8d495432
4 changed files with 97 additions and 6 deletions
8
src/features/guides/components/FileUpload.tsx
Normal file
8
src/features/guides/components/FileUpload.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
export default function FileUpload() {
|
||||||
|
return (
|
||||||
|
<label>
|
||||||
|
<span>File</span>
|
||||||
|
<input type="file" name="file" accept="images/*" multiple />
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
74
src/features/guides/hooks/useNewGuide.ts
Normal file
74
src/features/guides/hooks/useNewGuide.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Frame, type Guide, type Steps } from "../../../lib/schemas";
|
||||||
|
|
||||||
|
export default function useNewGuide() {
|
||||||
|
const [guide, setGuide] = useState<Guide>({
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
title: "",
|
||||||
|
description: "",
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
deletedAt: undefined,
|
||||||
|
});
|
||||||
|
const [frames, setFrames] = useState<Frame[]>([]);
|
||||||
|
const [steps, setSteps] = useState<Steps[]>([]);
|
||||||
|
|
||||||
|
const handleUpdateTitle = (title: string) => {
|
||||||
|
setGuide((prev) => ({ ...prev, title }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdateDescription = (description: string) => {
|
||||||
|
setGuide((prev) => ({ ...prev, description }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddFrames = (newFiles: File[]) => {
|
||||||
|
const newFrameObjects = newFiles.map((file) => ({
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
file: file,
|
||||||
|
guideId: guide.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
setFrames((prev) => [...prev, ...newFrameObjects]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveFrame = (frameId: string) => {
|
||||||
|
setFrames((prev) => prev.filter((frame) => frame.id !== frameId));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddSteps = (frameId: string) => {
|
||||||
|
setSteps((prev) => [
|
||||||
|
...prev,
|
||||||
|
{
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
text: "",
|
||||||
|
frameId: frameId,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdateStepText = (stepId: string, text: string) => {
|
||||||
|
setSteps((prev) =>
|
||||||
|
prev.map((step) => (step.id === stepId ? { ...step, text } : step)),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveStep = (stepId: string) => {
|
||||||
|
setSteps((prev) => prev.filter((step) => step.id !== stepId));
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
guide,
|
||||||
|
setGuide,
|
||||||
|
frames,
|
||||||
|
setFrames,
|
||||||
|
steps,
|
||||||
|
setSteps,
|
||||||
|
handleUpdateTitle,
|
||||||
|
handleUpdateDescription,
|
||||||
|
handleAddFrames,
|
||||||
|
handleAddSteps,
|
||||||
|
handleRemoveFrame,
|
||||||
|
handleUpdateStepText,
|
||||||
|
handleRemoveStep,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,20 @@
|
||||||
|
import FileUpload from "../components/FileUpload";
|
||||||
|
|
||||||
export default function NewGuide() {
|
export default function NewGuide() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>New Guide</h1>
|
<form>
|
||||||
|
<label>
|
||||||
|
<span>Title</span>
|
||||||
|
<input type="text" name="title" />
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>Description</span>
|
||||||
|
<textarea name="description" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<FileUpload />
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,26 +3,22 @@ import { z } from "zod";
|
||||||
export const StepsSchema = z.object({
|
export const StepsSchema = z.object({
|
||||||
id: z.uuid().default(() => crypto.randomUUID()),
|
id: z.uuid().default(() => crypto.randomUUID()),
|
||||||
text: z.string().min(1, "Text is required"),
|
text: z.string().min(1, "Text is required"),
|
||||||
order: z.number(),
|
|
||||||
frameId: z.uuid(),
|
frameId: z.uuid(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const FrameSchema = z.object({
|
export const FrameSchema = z.object({
|
||||||
id: z.uuid().default(() => crypto.randomUUID()),
|
id: z.uuid().default(() => crypto.randomUUID()),
|
||||||
file: z.instanceof(File),
|
file: z.instanceof(File),
|
||||||
steps: z.array(StepsSchema).min(1, "Steps are required"),
|
guideId: z.uuid(),
|
||||||
order: z.number(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const GuideSchema = z.object({
|
export const GuideSchema = z.object({
|
||||||
id: z.uuid().default(() => crypto.randomUUID()),
|
id: z.uuid().default(() => crypto.randomUUID()),
|
||||||
title: z.string().min(1, "Title is required"),
|
title: z.string().min(1, "Title is required"),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
content: z.string().optional(),
|
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date(),
|
updatedAt: z.date(),
|
||||||
deletedAt: z.date().optional(),
|
deletedAt: z.date().optional(),
|
||||||
frames: z.array(FrameSchema).min(1, "Frames are required"),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Steps = z.infer<typeof StepsSchema>;
|
export type Steps = z.infer<typeof StepsSchema>;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue