diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index f7fa87e..710fbc6 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,6 +1,8 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
+import { ThemeProvider } from "@/components/theme-provider";
+import { Toaster } from "@/components/ui/sonner";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -13,8 +15,8 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
+ title: "Image Web Exporter",
+ description: "Upload a picture, then export it in a different resolution and format.",
};
export default function RootLayout({
@@ -23,12 +25,20 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
-
+
- {children}
+
+ {children}
+
+
);
-}
+}
\ No newline at end of file
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 0d42a08..14fac05 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,12 +1,23 @@
+import { ImageConverter } from "@/components/image-converter";
import { MadeWithDyad } from "@/components/made-with-dyad";
export default function Home() {
return (
-
-
- Blank page
+
+
+
+
+ Image Web Exporter
+
+
+ Upload a picture, then export it in a different resolution and format.
+
+
+
-
+
+
+
);
-}
+}
\ No newline at end of file
diff --git a/src/components/image-converter.tsx b/src/components/image-converter.tsx
new file mode 100644
index 0000000..9ba97d6
--- /dev/null
+++ b/src/components/image-converter.tsx
@@ -0,0 +1,190 @@
+"use client";
+
+import { useState, useRef, ChangeEvent } from "react";
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from "@/components/ui/card";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Button } from "@/components/ui/button";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { Upload, Download, Image as ImageIcon } from "lucide-react";
+import { toast } from "sonner";
+
+export function ImageConverter() {
+ const [image, setImage] = useState(null);
+ const [previewUrl, setPreviewUrl] = useState(null);
+ const [width, setWidth] = useState("");
+ const [height, setHeight] = useState("");
+ const [format, setFormat] = useState<"png" | "jpeg" | "webp">("png");
+ const [isConverting, setIsConverting] = useState(false);
+ const fileInputRef = useRef(null);
+
+ const handleImageChange = (e: ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (file && file.type.startsWith("image/")) {
+ setImage(file);
+ const reader = new FileReader();
+ reader.onload = (event) => {
+ const img = new Image();
+ img.onload = () => {
+ setWidth(img.width);
+ setHeight(img.height);
+ };
+ img.src = event.target?.result as string;
+ setPreviewUrl(URL.createObjectURL(file));
+ };
+ reader.readAsDataURL(file);
+ } else {
+ toast.error("Please select a valid image file.");
+ }
+ };
+
+ const handleConvertAndDownload = () => {
+ if (!image || !previewUrl || !width || !height) {
+ toast.error("Please upload an image and set dimensions.");
+ return;
+ }
+
+ setIsConverting(true);
+ const img = new Image();
+ img.crossOrigin = "anonymous";
+ img.src = previewUrl;
+
+ img.onload = () => {
+ const canvas = document.createElement("canvas");
+ canvas.width = Number(width);
+ canvas.height = Number(height);
+ const ctx = canvas.getContext("2d");
+
+ if (ctx) {
+ ctx.drawImage(img, 0, 0, Number(width), Number(height));
+ const dataUrl = canvas.toDataURL(`image/${format}`);
+ const link = document.createElement("a");
+ link.href = dataUrl;
+ const originalName = image.name.substring(0, image.name.lastIndexOf('.'));
+ link.download = `${originalName}_${width}x${height}.${format}`;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ toast.success("Image exported successfully!");
+ } else {
+ toast.error("Could not process the image.");
+ }
+ setIsConverting(false);
+ };
+
+ img.onerror = () => {
+ toast.error("Failed to load image for conversion.");
+ setIsConverting(false);
+ };
+ };
+
+ return (
+
+
+ Image Settings
+
+ Adjust the resolution and format for your export.
+
+
+
+ {previewUrl ? (
+
+
+
+ ) : (
+ fileInputRef.current?.click()}
+ >
+
+ Click to upload an image
+
+ )}
+
+
+
+
+
+
+ Format
+ setFormat(value)}
+ disabled={!image}
+ >
+
+
+
+
+ PNG
+ JPEG
+ WEBP
+
+
+
+
+
+ fileInputRef.current?.click()}
+ >
+
+ {image ? "Change Image" : "Upload Image"}
+
+
+
+ {isConverting ? "Converting..." : "Convert & Download"}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx
new file mode 100644
index 0000000..4203e6a
--- /dev/null
+++ b/src/components/theme-provider.tsx
@@ -0,0 +1,9 @@
+"use client"
+
+import * as React from "react"
+import { ThemeProvider as NextThemesProvider } from "next-themes"
+import { type ThemeProviderProps } from "next-themes/dist/types"
+
+export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
+ return {children}
+}
\ No newline at end of file