Next.js
Install requirements
Zod
Zod is a TypeScript-first schema declaration and validation library.
https://www.npmjs.com/package/zod
app\page.tsx
//app\page.tsx import Image from "next/image"; import fs from "node:fs/promises"; import Link from "next/link"; export default async function Home() { const files = await fs.readdir("./public/assets"); const images = files .map((file) => `/assets/${file}`); return ( <div className="max-w-screen-lg mx-auto py-14"> <div className="flex items-end justify-between"> <h1 className="text-4xl font-bold">Images</h1> <Link href="/upload" className="py-3 px-6 bg-blue-600 hover:bg-blue-700 text-white" > Upload New Image </Link> </div> <div className="grid md:grid-cols-3 gap-5 mt-10"> {images.map((image) => ( <div key={image} className="max-w-sm border border-gray-200 rounded-md shadow"> <div className="relative aspect-video"> <Image key={image} src={image} alt={image} fill priority sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw" className="rounded-t-md object-cover" /> </div> </div> ))} </div> </div> ); }app\upload\page.tsx
//app\upload\page.tsx import UploadForm from "@/components/upload-form"; const UploadPage = () => { return ( <div className="min-h-screen flex items-center justify-center bg-slate-100"> <div className="bg-white rounded-sm shadow p-8"> <h1 className="text-2xl font-bold mb-5">Upload Image</h1> <UploadForm /> </div> </div> ); }; export default UploadPage;components\upload-form.tsx
//components\upload-form.tsx "use client"; import React from "react"; import { uploadImage } from "@/lib/actions"; import { useFormState } from "react-dom"; import { SubmitButton } from "@/components/button"; const UploadForm = () => { const [state, formAction] = useFormState(uploadImage, null); return ( <form action={formAction}> {/* Alert */} {state?.message ? ( <div className="p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50" role="alert" > <div className="font-medium">{state?.message}</div> </div> ) : null} <div className="mb-4 pt-2"> <input type="file" name="image" className="file:py-2 file:px-4 file:mr-4 file:rounded-sm file:border-0 file:bg-gray-200 hover:file:bg-gray-300 file:cursor-pointer border border-gray-400 w-full" /> <div aria-live="polite" aria-atomic="true"> <p className="text-sm text-red-500 mt-2">{state?.error?.image}</p> </div> </div> <div className="mb-4 pt-4"> <SubmitButton label="upload" /> </div> </form> ); }; export default UploadForm;components\button.tsx
//components\button.tsx "use client"; import { useFormStatus } from "react-dom"; import { clsx } from "clsx"; export const SubmitButton = ({ label }: { label: string }) => { const { pending } = useFormStatus(); return ( <button className={clsx( "bg-blue-700 text-white w-full font-medium py-2.5 px-6 text-base rounded-sm hover:bg-blue-600", { "opacity-50 cursor-progress": pending, } )} type="submit" disabled={pending} > {label === "upload" ? ( <>{pending ? "Uploading..." : "Upload"}</> ) : ( <>{pending ? "Updating..." : "Update"}</> )} </button> ); };lib\actions.ts
//lib\actions.ts "use server"; import { z } from "zod"; //https://www.npmjs.com/package/zod import { writeFile } from "fs/promises"; import path from "path"; import { redirect } from 'next/navigation' import { revalidatePath } from 'next/cache' const UploadSchema = z.object({ image: z .instanceof(File) .refine((file) => file.size > 0, { message: "Image is required" }) .refine((file) => file.size === 0 || file.type.startsWith("image/"), { message: "Only images are allowed", }) .refine((file) => file.size < 4000000, { message: "Image must less than 4MB", }), }); export const uploadImage = async (prevState: unknown, formData: FormData) => { const validatedFields = UploadSchema.safeParse( Object.fromEntries(formData.entries()) ); if (!validatedFields.success) { return { error: validatedFields.error.flatten().fieldErrors, }; } const file = formData.get("image"); try { const buffer = Buffer.from(await file.arrayBuffer()); const filename = file.name.replaceAll(" ", "_"); console.log(filename); await writeFile( path.join(process.cwd(), "public/assets/" + filename), buffer ); //return { message: "Success" }; } catch (error) { console.log("Error occured ", error); return { message: "Failed" }; } revalidatePath("/"); redirect("/"); };run C:\nextjs>npm run dev