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
