| "use client"; |
|
|
| import { FormEvent, useCallback, useEffect, useState } from "react"; |
| import { TextInputWithLabel } from "../text-input-with-label"; |
| import { Product } from "@/db/schema"; |
| import { useRouter } from "next/navigation"; |
| import { Button } from "../ui/button"; |
| import { secondLevelNestedRoutes, singleLevelNestedRoutes } from "@/lib/routes"; |
| import { toast } from "../ui/use-toast"; |
| import { HeadingAndSubheading } from "./heading-and-subheading"; |
| import { ProductImages } from "@/lib/types"; |
| import { ProductImageUploader } from "./product-image-uploader"; |
| import type { deleteProduct, updateProduct } from "@/server-actions/products"; |
| import { Loader2 } from "lucide-react"; |
|
|
| const defaultValues = { |
| name: "", |
| description: "", |
| price: "", |
| inventory: "", |
| images: [], |
| }; |
|
|
| export const ProductEditorElements = (props: { |
| displayType?: "page" | "modal"; |
| productStatus: "new-product" | "existing-product"; |
| productActions: { |
| updateProduct: typeof updateProduct; |
| deleteProduct: typeof deleteProduct; |
| }; |
| initialValues?: Product; |
| }) => { |
| const router = useRouter(); |
| const [isLoading, setIsLoading] = useState(false); |
| const [imagesToDelete, setImagesToDelete] = useState([] as ProductImages[]); |
| const [newImages, setNewImages] = useState([] as ProductImages[]); |
|
|
| const [formValues, setFormValues] = useState<Omit<Product, "id" | "storeId">>( |
| props.initialValues ?? defaultValues |
| ); |
|
|
| const dismissModal = useCallback(() => { |
| if (props.displayType === "modal") { |
| router.back(); |
| } else { |
| router.push(singleLevelNestedRoutes.account.products); |
| } |
| }, [router, props.displayType]); |
|
|
| const onKeyDown = useCallback( |
| (e: any) => { |
| if (e.key === "Escape") dismissModal(); |
| }, |
| [dismissModal] |
| ); |
|
|
| useEffect(() => { |
| document.addEventListener("keydown", onKeyDown); |
| return () => document.removeEventListener("keydown", onKeyDown); |
| }, [onKeyDown]); |
|
|
| const handleProductUpdate = async ( |
| e: |
| | FormEvent<HTMLFormElement> |
| | React.MouseEvent<HTMLButtonElement, MouseEvent>, |
| buttonAction?: "delete" |
| ) => { |
| e.preventDefault(); |
| setIsLoading(true); |
|
|
| let data; |
| if (buttonAction === "delete") { |
| |
| data = await props.productActions.deleteProduct(props.initialValues?.id); |
| if (!data.error) { |
| router.refresh(); |
| router.push(singleLevelNestedRoutes.account.products); |
| } |
| } else if (props.initialValues) { |
| |
| const updatedValues = { |
| ...formValues, |
| images: [ |
| ...(props.initialValues?.images as []), |
| ...(newImages ?? []), |
| ].filter((item) => imagesToDelete && !imagesToDelete.includes(item)), |
| } as Omit<Product, "storeId">; |
| data = await props.productActions.updateProduct(updatedValues); |
| if (!data.error) { |
| router.refresh(); |
| router.push(singleLevelNestedRoutes.account.products); |
| } |
| } else { |
| |
| const res = await fetch("/api/product", { |
| method: "POST", |
| body: JSON.stringify({ |
| ...formValues, |
| images: newImages, |
| }), |
| }); |
| data = (await res.json()) as unknown as { |
| error: boolean; |
| message: string; |
| action: string; |
| productId?: string; |
| }; |
| console.log(data); |
| if (data.productId) { |
| router.push( |
| `${secondLevelNestedRoutes.product.base}/${data.productId}` |
| ); |
| } |
| setFormValues(defaultValues); |
| } |
| setIsLoading(false); |
| toast({ |
| title: data.message, |
| description: data.action, |
| }); |
| }; |
|
|
| return ( |
| <> |
| <HeadingAndSubheading |
| heading={ |
| props.productStatus === "new-product" |
| ? "Create a new product" |
| : "Edit product" |
| } |
| subheading={ |
| props.productStatus === "new-product" |
| ? "Enter the details of your new product below and click save." |
| : "Edit the details of your product below and click save." |
| } |
| /> |
| |
| <form onSubmit={handleProductUpdate}> |
| <div className="flex flex-col gap-8 mt-2 mb-6"> |
| <TextInputWithLabel |
| required |
| id="name" |
| label="Product Name" |
| type="text" |
| state={formValues} |
| setState={setFormValues} |
| /> |
| <TextInputWithLabel |
| id="description" |
| label="Description" |
| type="text" |
| inputType="textarea" |
| rows={8} |
| state={formValues} |
| setState={setFormValues} |
| /> |
| <ProductImageUploader |
| product={{ |
| name: formValues.name, |
| images: (props.initialValues?.images as ProductImages[]) ?? [], |
| }} |
| newImages={newImages} |
| setNewImages={setNewImages} |
| imagesToDelete={imagesToDelete} |
| setImagesToDelete={setImagesToDelete} |
| /> |
| <div className="grid grid-cols-2 gap-4"> |
| <TextInputWithLabel |
| id="price" |
| label="Price" |
| type="number" |
| state={formValues} |
| setState={setFormValues} |
| /> |
| <TextInputWithLabel |
| id="inventory" |
| label="Quantity In Stock" |
| type="number" |
| state={formValues} |
| setState={setFormValues} |
| /> |
| </div> |
| </div> |
| <div className="flex justify-between items-center"> |
| {!!props.initialValues && ( |
| <Button |
| type="button" |
| variant="destructiveOutline" |
| onClick={(e) => handleProductUpdate(e, "delete")} |
| > |
| Delete |
| </Button> |
| )} |
| <div className="flex items-center gap-2 ml-auto"> |
| <Button type="button" variant="outline" onClick={dismissModal}> |
| Cancel |
| </Button> |
| <Button |
| type="submit" |
| disabled={isLoading} |
| className="flex gap-2 items-center justify-center" |
| > |
| {!!isLoading && <Loader2 size={18} className="animate-spin" />} |
| {props.initialValues ? "Save" : "Create"} |
| </Button> |
| </div> |
| </div> |
| </form> |
| </> |
| ); |
| }; |
|
|