diff --git a/components/additem/AddItemForm.module.css b/components/additem/AddItemForm.module.css new file mode 100644 index 00000000..9694017b --- /dev/null +++ b/components/additem/AddItemForm.module.css @@ -0,0 +1,25 @@ +.form { + display: flex; + flex-direction: column; + gap: 24px; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.title { + font-size: 1.25rem; + font-weight: 700; +} + +.button { + padding: 12px 23px 12px 23px; + line-height: 1.125rem; +} + +.tags { + margin-top: -10px; +} diff --git a/components/additem/AddItemForm.tsx b/components/additem/AddItemForm.tsx new file mode 100644 index 00000000..d993cbac --- /dev/null +++ b/components/additem/AddItemForm.tsx @@ -0,0 +1,184 @@ +import { + useState, + useEffect, + ChangeEvent, + KeyboardEvent, + MouseEvent, +} from "react"; +import { useRouter } from "next/router"; +import { useMutation } from "@tanstack/react-query"; +import FileInput from "../ui/FileInput"; +import Input from "../ui/Input"; +import Textarea from "../ui/Textarea"; +import Button from "../ui/Button"; +import Tags from "../ui/Tags"; +import { uploadImage } from "@/lib/imageService"; +import { addProduct } from "@/lib/productService"; +import styles from "./AddItemForm.module.css"; + +interface ProductFormValues { + imgFile: File | null; + product: string; + description: string; + price: string; + tags: string[]; +} + +type ProductField = keyof ProductFormValues; + +export type CreateProductRequestBody = Omit & { + imgUrl: string; +}; + +const INITIAL_PRODUCT = { + imgFile: null, + product: "", + description: "", + price: "", + tags: [], +}; + +const AddItemForm = () => { + const [isDisabled, setIsDisabled] = useState(true); + const [values, setValues] = useState(INITIAL_PRODUCT); + const router = useRouter(); + + const uploadImageMutation = useMutation({ + mutationFn: (imgFile: File) => uploadImage(imgFile), + }); + + const addProductMutation = useMutation({ + mutationFn: (formValues: CreateProductRequestBody) => + addProduct(formValues), + onSuccess: (id) => { + router.push(`/items/${id}`); + }, + }); + + const handleChange = ( + name: string, + value: ProductFormValues[ProductField] + ) => { + setValues((prevValues) => { + return { + ...prevValues, + [name]: value, + }; + }); + }; + + const handleInputChange = ( + e: ChangeEvent | ChangeEvent + ) => { + const { name, value } = e.target; + handleChange(name, value); + }; + + const checkFormEmpty = (values: ProductFormValues) => { + const { imgFile, ...otherValues } = values; + + return Object.values(otherValues).some((value) => { + if (Array.isArray(value)) { + return value.length === 0; + } + return value === ""; + }); + }; + + const handleSubmit = async ( + e: MouseEvent + ): Promise => { + e.preventDefault(); + + const { imgFile, ...otherValues } = values; + let imgUrl = "https://example.com/..."; + + if (imgFile) { + imgUrl = await uploadImageMutation.mutateAsync(imgFile); + } + + addProductMutation.mutate({ imgUrl, ...otherValues }); + }; + + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Enter" && e.nativeEvent.isComposing === false) { + e.preventDefault(); + e.stopPropagation(); + + const { name, value } = e.currentTarget; + e.currentTarget.value = ""; + if (values.tags.includes(value) || value.trim() === "") return; + + handleChange(name, [...values.tags, value]); + } + }; + + const handleTagRemove = ( + e: MouseEvent, + target: string + ) => { + e.preventDefault(); + const nextValue: string[] = values.tags.filter((tag) => tag !== target); + handleChange("tags", nextValue); + }; + + useEffect(() => { + setIsDisabled(checkFormEmpty(values)); + }, [values]); + + return ( +
+
+

상품 등록하기

+ +
+ + +