added forms for adding producer.
All checks were successful
Vercel Preview Deployment / Deploy-Preview (push) Successful in 1m19s

added delete button to all categories.
This commit is contained in:
christian 2024-06-04 16:02:29 +02:00
parent a609300688
commit 14fa130ce6
16 changed files with 392 additions and 45 deletions

View File

@ -4,8 +4,10 @@ import CreateCountry from "./_components/admin/CreateCountry";
import AllCountries from "./_components/AllCountries"; import AllCountries from "./_components/AllCountries";
import CreateRegion from "./_components/admin/CreateRegion"; import CreateRegion from "./_components/admin/CreateRegion";
import AllRegions from "./_components/AllRegions"; import AllRegions from "./_components/AllRegions";
import CreateSubRegionForm from "./_components/admin/CreateSubRegionForm";
import CreateSubRegion from "./_components/admin/CreateSubRegion"; import CreateSubRegion from "./_components/admin/CreateSubRegion";
import AllSubRegions from "./_components/AllSubRegions";
import CreateProducer from "./_components/admin/CreateProducer";
import AllProducers from "./_components/allProducers";
export default function App() { export default function App() {
return ( return (
@ -17,6 +19,9 @@ export default function App() {
<CreateRegion /> <CreateRegion />
<AllRegions /> <AllRegions />
<CreateSubRegion /> <CreateSubRegion />
<AllSubRegions />
<CreateProducer />
<AllProducers />
</div> </div>
); );
} }

View File

@ -1,3 +1,5 @@
import { Delete } from "lucide-react";
import deleteRegion from "~/server/actions/deleteRegion";
import getAllCountries from "~/server/actions/getAllCountries"; import getAllCountries from "~/server/actions/getAllCountries";
import getAllRegions from "~/server/actions/getAllRegions"; import getAllRegions from "~/server/actions/getAllRegions";
@ -7,24 +9,16 @@ export default async function AllRegions() {
return ( return (
<div className="pt-4"> <div className="pt-4">
<h1 className="text-2xl">All Regions:</h1> <h1 className="text-2xl">All Regions:</h1>
{allRegions[0] ? (
<>
<ul>
{allRegions.map((region) => ( {allRegions.map((region) => (
<li key={region.id}> <div key={region.id} className="flex gap-1">
{region.name} -{" "} <p>{region.name}</p>
{ <form action={deleteRegion.bind(null, region.id)}>
allCountries.find( <button>
(country) => country.id === region.countryId, <Delete />
)?.name </button>
} </form>
</li> </div>
))} ))}
</ul>
</>
) : (
<p>There are no regions in the db.</p>
)}
</div> </div>
); );
} }

View File

@ -0,0 +1,22 @@
import { Delete } from "lucide-react";
import deleteSubRegion from "~/server/actions/deleteSubRegion";
import getAllSubRegions from "~/server/actions/getAllSubRegions";
export default async function AllSubRegions() {
const allSubRegions = await getAllSubRegions();
return (
<div className="pt-4">
<h1 className="text-2xl">All Sub Regions:</h1>
{allSubRegions.map((subRegion) => (
<div key={subRegion.id} className="flex gap-1">
<p>{subRegion.name}</p>
<form action={deleteSubRegion.bind(null, subRegion.id)}>
<button>
<Delete />
</button>
</form>
</div>
))}
</div>
);
}

View File

@ -0,0 +1,13 @@
"use server";
import getAllCountries from "~/server/actions/getAllCountries";
import CreateProducerForm from "./CreateProducerForm";
export default async function CreateProducer() {
const allCountries = await getAllCountries();
return (
<div className="container flex w-full flex-col justify-center py-4">
<h1 className="pt-4 text-2xl">Fill the form to create a new producer</h1>
<CreateProducerForm allCountries={allCountries} />
</div>
);
}

View File

@ -0,0 +1,108 @@
"use client";
import clsx from "clsx";
import { Check, CircleX } from "lucide-react";
import { useFormState } from "react-dom";
import { Input } from "~/components/ui/input";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "~/components/ui/select";
import { Textarea } from "~/components/ui/textarea";
import { addProducer } from "~/server/actions/addProducer";
import SubmitButton from "../SubmitButton";
type Country = {
id: string;
name: string;
};
export default function CreateProducerForm(props: { allCountries: Country[] }) {
const { allCountries } = props;
const [formState, formAction] = useFormState(addProducer, {
message: "",
errors: undefined,
fieldValues: {
name: "",
description: "",
imageUrl: "",
countryId: "",
},
});
return (
<form action={formAction}>
<Input
name="name"
id="name"
placeholder="Enter the name of the producer..."
className={clsx({ "border-red-500": formState.errors?.name })}
/>
{formState.message !== "" && !formState.errors?.name ? (
<Check className="text-green-500" />
) : (
""
)}
{formState.errors?.name ? (
<div className="flex min-w-96 items-center gap-1 text-red-500">
<CircleX />
<span className="text-sm">{formState.errors?.name}</span>
</div>
) : (
""
)}
<Textarea
name="description"
id="description"
placeholder="Enter a description of the producer..."
className={clsx({ "border-red-500": formState.errors?.description })}
/>
{formState.message !== "" && !formState.errors?.description ? (
<Check className="text-green-500" />
) : (
""
)}
{formState.errors?.description ? (
<div className="flex min-w-96 items-center gap-1 text-red-500">
<CircleX />
<span className="text-sm">{formState.errors?.description}</span>
</div>
) : (
""
)}
<Input
name="imageUrl"
id="imageUrl"
placeholder="Image URL"
className={clsx({ "border-red-500": formState.errors?.imageUrl })}
/>
{formState.message !== "" && !formState.errors?.imageUrl ? (
<Check className="text-green-500" />
) : (
""
)}
{formState.errors?.imageUrl ? (
<div className="flex min-w-96 items-center gap-1 text-red-500">
<CircleX />
<span className="text-sm">{formState.errors?.imageUrl}</span>
</div>
) : (
""
)}
<Select name="countryId">
<SelectTrigger className={`w-[180px] `}>
<SelectValue placeholder="select country" />
</SelectTrigger>
<SelectContent>
{allCountries.map((country) => (
<SelectItem key={country.id} value={country.id}>
{country.name}
</SelectItem>
))}
</SelectContent>
</Select>
<SubmitButton text={"producer"} />
</form>
);
}

View File

@ -1,5 +1,4 @@
"use server"; "use server";
import { db } from "~/server/db";
import CreateRegionForm from "./CreateRegionForm"; import CreateRegionForm from "./CreateRegionForm";
import getAllCountries from "~/server/actions/getAllCountries"; import getAllCountries from "~/server/actions/getAllCountries";

View File

@ -1,6 +1,6 @@
"use client"; "use client";
import clsx from "clsx"; import clsx from "clsx";
import { ChangeEvent, useEffect, useState } from "react"; import { ChangeEvent, useEffect, useRef, useState } from "react";
import { useFormState } from "react-dom"; import { useFormState } from "react-dom";
import { Input } from "~/components/ui/input"; import { Input } from "~/components/ui/input";
import { import {
@ -12,6 +12,7 @@ import {
} from "~/components/ui/select"; } from "~/components/ui/select";
import { addSubRegion } from "~/server/actions/addSubRegion"; import { addSubRegion } from "~/server/actions/addSubRegion";
import SubmitButton from "../SubmitButton"; import SubmitButton from "../SubmitButton";
import { Check, CircleX } from "lucide-react";
type Region = { type Region = {
id: string; id: string;
@ -51,16 +52,22 @@ export default function CreateSubRegionForm(props: {
setSelectCountryRegions(regions); setSelectCountryRegions(regions);
} }
}, [selectCountryId]); }, [selectCountryId]);
const formRef = useRef<HTMLFormElement>(null);
useEffect(() => {
if (formState.message === "success") {
formRef.current?.reset();
}
}, [formState.message]);
return ( return (
<div> <div className="flex flex-col gap-2">
{/* country selector */} {/* country selector */}
<p>{selectCountryId}</p>
<Select <Select
name="country" name="country"
onValueChange={(value) => setSelectCountryId(value)} onValueChange={(value) => setSelectCountryId(value)}
> >
<SelectTrigger className={`w-[180px] `}> <SelectTrigger className={`w-[180px] `}>
<SelectValue placeholder="Country" /> <SelectValue placeholder="select country" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{allCountries.map((country) => ( {allCountries.map((country) => (
@ -70,13 +77,14 @@ export default function CreateSubRegionForm(props: {
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
<form action={formActions}> <form ref={formRef} action={formActions}>
{/* region selector */} {/* region selector */}
<div className="flex max-w-3xl items-center gap-2">
<Select name="regionId"> <Select name="regionId">
<SelectTrigger <SelectTrigger
className={`w-[180px] ${clsx({ "border-red-500": formState.errors?.regionId })}`} className={`w-[180px] ${clsx({ "border-red-500": formState.errors?.regionId })}`}
> >
<SelectValue placeholder="Region" /> <SelectValue placeholder="select region" />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{selectCountryRegions.map((region) => ( {selectCountryRegions.map((region) => (
@ -86,12 +94,41 @@ export default function CreateSubRegionForm(props: {
))} ))}
</SelectContent> </SelectContent>
</Select> </Select>
{formState.message === "success" && !formState.errors?.regionId ? (
<Check className="text-green-500" />
) : (
""
)}
{formState.errors?.regionId ? (
<div className="flex min-w-40 items-center gap-1 text-red-500">
<CircleX className="text-red-500" />
<span className="text-sm">{formState.errors?.regionId}</span>
</div>
) : (
""
)}
</div>
<div className="flex max-w-3xl items-center gap-2">
<Input <Input
name="name" name="name"
id="name" id="name"
placeholder="Name" placeholder="Name the sub region"
className={`${clsx({ "border-red-500": formState.errors?.name })}`} className={`${clsx({ "border-red-500": formState.errors?.name })}`}
/> />
{formState.message === "success" && !formState.errors?.name ? (
<Check className="text-green-500" />
) : (
""
)}
{formState.errors?.name ? (
<div className="flex min-w-40 items-center gap-1 text-red-500">
<CircleX className="text-red-500" />
<span className="text-sm">{formState.errors?.name}</span>
</div>
) : (
""
)}
</div>
<SubmitButton text={"sub region"} /> <SubmitButton text={"sub region"} />
</form> </form>
</div> </div>

View File

@ -0,0 +1,22 @@
import { Delete } from "lucide-react";
import deleteProducer from "~/server/actions/deleteProducer";
import getAllProducers from "~/server/actions/getAllProducers";
export default async function AllProducers() {
const allProducers = await getAllProducers();
return (
<div className="pt-4">
<h1 className="text-2xl">All Producers:</h1>
{allProducers.map((producer) => (
<div key={producer.id} className="flex gap-1">
<p>{producer.name}</p>
<form action={deleteProducer.bind(null, producer.id)}>
<button>
<Delete />
</button>
</form>
</div>
))}
</div>
);
}

View File

@ -0,0 +1,24 @@
import * as React from "react"
import { cn } from "~/lib/utils"
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[80px] w-full rounded-md border border-slate-200 bg-white px-3 py-2 text-sm ring-offset-white placeholder:text-slate-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-800 dark:bg-slate-950 dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus-visible:ring-slate-300",
className
)}
ref={ref}
{...props}
/>
)
}
)
Textarea.displayName = "Textarea"
export { Textarea }

View File

@ -0,0 +1,77 @@
"use server";
import { eq } from "drizzle-orm";
import { db } from "../db";
import { producers } from "../db/schema";
import { ZodError, z } from "zod";
import { revalidatePath } from "next/cache";
export const addProducer = async (prevstate: any, formData: FormData) => {
//assign formdaata to variables.
const name = (formData.get("name") as string).toLowerCase();
const description = (formData.get("description") as string).toLowerCase();
const imageUrl = (formData.get("imageUrl") as string).toLowerCase();
const countryId = formData.get("countryId") as string;
//check if producer already exists in country
const exists = await db
.select({ name: producers.name })
.from(producers)
.where(eq(producers.name, name));
//Define the schema for the form data
const schema = z.object({
name: z
.string()
.min(1, "Name is required")
.refine(() => !exists[0], {
message: `${name} already exists in selected country`,
}),
description: z.string(),
imageUrl: z.string(),
countryId: z.string().min(1, "No country selected"),
});
//Parse the form data using the schema for validation, and check if the name already exists
try {
schema.parse({
name,
description,
imageUrl,
countryId,
});
//If the name doesn't exist, add the country to the database abd revalidate the page
await db
.insert(producers)
.values({ name, description, imageUrl, countryId });
revalidatePath("/");
//Return a success message
return {
message: "success",
errors: undefined,
fieldValues: {
name: "",
description: "",
imageUrl: "",
countryId: "",
},
};
} catch (error) {
const zodError = error as ZodError;
const errorMap = zodError.flatten().fieldErrors;
//Return an error object with the field values and errors.
return {
message: "error",
errors: {
name: errorMap["name"]?.[0] ?? "",
description: errorMap["description"]?.[0] ?? "",
imageUrl: errorMap["imageUrl"]?.[0] ?? "",
countryId: errorMap["countryId"]?.[0] ?? "",
},
fieldValues: {
name,
description,
imageUrl,
countryId,
},
};
}
};

View File

@ -4,6 +4,7 @@ import { eq } from "drizzle-orm";
import { db } from "../db"; import { db } from "../db";
import { subRegions } from "../db/schema"; import { subRegions } from "../db/schema";
import { ZodError, z } from "zod"; import { ZodError, z } from "zod";
import { revalidatePath } from "next/cache";
export const addSubRegion = async (prevstate: any, formData: FormData) => { export const addSubRegion = async (prevstate: any, formData: FormData) => {
//assign formdaata to variables. //assign formdaata to variables.
@ -34,6 +35,7 @@ export const addSubRegion = async (prevstate: any, formData: FormData) => {
}); });
//If the name doesn't exist, add the country to the database abd revalidate the page //If the name doesn't exist, add the country to the database abd revalidate the page
await db.insert(subRegions).values({ regionId, name }); await db.insert(subRegions).values({ regionId, name });
revalidatePath("/");
//Return a success message //Return a success message
return { return {
message: "success", message: "success",

View File

@ -0,0 +1,10 @@
"use server";
import { eq } from "drizzle-orm";
import { db } from "../db";
import { producers } from "../db/schema";
import { revalidatePath } from "next/cache";
export default async function deleteProducer(id: string) {
await db.delete(producers).where(eq(producers.id, id));
revalidatePath("/");
}

View File

@ -0,0 +1,10 @@
"use server";
import { eq } from "drizzle-orm";
import { db } from "../db";
import { regions } from "../db/schema";
import { revalidatePath } from "next/cache";
export default async function deleteRegion(id: string) {
await db.delete(regions).where(eq(regions.id, id));
revalidatePath("/");
}

View File

@ -0,0 +1,10 @@
"use server";
import { eq } from "drizzle-orm";
import { db } from "../db";
import { subRegions } from "../db/schema";
import { revalidatePath } from "next/cache";
export default async function deleteSubRegion(id: string) {
await db.delete(subRegions).where(eq(subRegions.id, id));
revalidatePath("/");
}

View File

@ -0,0 +1,7 @@
"use server";
import { db } from "../db";
export default async function getAllProducers() {
const allProducers = await db.query.producers.findMany();
return allProducers;
}

View File

@ -0,0 +1,7 @@
"use server";
import { db } from "../db";
export default async function getAllSubRegions() {
const allSubRegions = await db.query.subRegions.findMany();
return allSubRegions;
}