Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions app/generate/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"use client";

import { useState } from "react";

export default function UploadPage() {
const [file, setFile] = useState<File | null>(null);
const [data, setData] = useState<string | null>(null);

const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
setFile(e.target.files[0]);
}
};

const handleUpload = async () => {
if (!file) return;

const formData = new FormData();
formData.append("image", file);

const res = await fetch("/api/upload", {
method: "POST",
body: formData,
});

const data = await res.json();
setData(data.url + "\n\n" + data.svg);
console.log("Response:", data);
};

return (
<div>
<input type="file" accept="image/*" onChange={handleFileChange} />
<button onClick={handleUpload}>Upload</button>
<p>{data}</p>
</div>
);
}
2 changes: 2 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Navbar from "@/components/NavbarComponents/Navbar";
import { ThemeProvider } from "@/components/NavbarComponents/ThemeProvider";
import { Space_Grotesk } from "next/font/google";
import "./globals.css";
import Footer from "@/components/Footer/Footer";

const spaceGrotesk = Space_Grotesk({
variable: "--font-space-grotesk",
Expand All @@ -25,6 +26,7 @@ export default function RootLayout({
>
<Navbar />
<main>{children}</main>
<Footer />
</ThemeProvider>
</body>
</html>
Expand Down
37 changes: 3 additions & 34 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,7 @@
"use client";

import { useState } from "react";
import HeroSection from "@/components/Home/HeroSection";

export default function UploadPage() {
const [file, setFile] = useState<File | null>(null);
const [data, setData] = useState<string | null>(null);

const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
setFile(e.target.files[0]);
}
};

const handleUpload = async () => {
if (!file) return;

const formData = new FormData();
formData.append("image", file);

const res = await fetch("/api/upload", {
method: "POST",
body: formData,
});

const data = await res.json();
setData(data.url + "\n\n" + data.svg);
console.log("Response:", data);
};

return (
<div>
<input type="file" accept="image/*" onChange={handleFileChange} />
<button onClick={handleUpload}>Upload</button>
<p>{data}</p>
</div>
);
export default function Page() {
return <HeroSection />;
}
20 changes: 20 additions & 0 deletions components/Footer/Footer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"use client";

import Link from "next/link";

const Footer = () => {
const currentYear = new Date().getFullYear();

return (
<footer className="z-50 bg-white dark:bg-neutral-900 border-t border-slate-200 dark:border-neutral-800 text-center w-full mx-auto px-4 sm:px-6 lg:px-8 pt-6 pb-6">
<p className="text-xs text-slate-500 dark:text-slate-400">
© {currentYear} SVG From Img. All rights reserved. | Made with ❤️ by{" "}
<Link className="underline" href="https://github.com/ShitanshuKumar607">
Shitanshu Kumar
</Link>
</p>
</footer>
);
};

export default Footer;
116 changes: 116 additions & 0 deletions components/Home/HeroSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"use client";

import { Button } from "@/components/ui/button";
import { motion, Variants } from "framer-motion";
import { ArrowRight, Trophy } from "lucide-react";
import Link from "next/link";

const container: Variants = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.3,
},
},
};

const item: Variants = {
hidden: { opacity: 0, y: 20 },
show: {
opacity: 1,
y: 0,
transition: {
type: "spring",
stiffness: 100,
damping: 15,
},
},
};

const HeroSection = () => {
return (
<section className="relative overflow-hidden bg-zinc-50 dark:bg-neutral-900/80">
{/* Accent Corner Lines */}
<div className="hidden sm:block absolute top-10 left-10 h-16 w-16 border-t-2 border-l-2 border-emerald-400 dark:border-violet-500 opacity-60 rounded-tl-lg" />
<div className="hidden sm:block absolute bottom-10 right-10 h-20 w-20 border-b-2 border-r-2 border-emerald-400 dark:border-violet-500 opacity-60 rounded-br-lg" />

{/* Geometric Shape Decorations */}
<div className="absolute top-1/3 -left-6 w-24 h-24 border border-emerald-300 dark:border-violet-400/40 rounded-full opacity-30" />
<div className="absolute bottom-1/4 -right-8 w-20 h-20 border border-emerald-300 dark:border-violet-400/40 rotate-12 opacity-30" />

{/* Hero Content */}
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-20 md:py-32 relative">
<motion.div
className="text-center"
initial="hidden"
animate="show"
variants={container}
>
{/* Badge */}
<motion.div
variants={item}
className="inline-flex items-center gap-2 bg-emerald-100 text-emerald-700 dark:bg-violet-700/30 dark:text-violet-300 text-sm font-medium px-4 py-2 rounded-full mb-6"
>
<Trophy className="h-4 w-4" />
<span>Fast & Secure Converter</span>
</motion.div>

{/* Heading */}
<motion.h1
className="text-4xl sm:text-6xl font-bold tracking-tight max-w-4xl mx-auto leading-tight mb-6 text-slate-900 dark:text-neutral-100"
variants={item}
>
Your Ultimate
<span className="text-emerald-600 dark:text-violet-500"> SVG </span>
Companion
</motion.h1>

{/* Subtext */}
<motion.p
className="text-lg text-slate-600 dark:text-neutral-400 max-w-3xl mx-auto lg:text-xl mb-10"
variants={item}
>
Effortlessly transform your PNG, JPG, and other raster images into
high-quality, editable SVG vectors with a single click. Perfect for
any creator.
</motion.p>

<motion.div
className="flex flex-col sm:flex-row gap-4 justify-center"
variants={{
hidden: { opacity: 0, y: 20 },
show: {
opacity: 1,
y: 0,
transition: {
staggerChildren: 0.1,
delayChildren: 0.4,
},
},
}}
>
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.98 }}
variants={item}
>
<Link href="/generate">
<Button
size="lg"
className="gap-2 bg-emerald-600 text-white hover:bg-emerald-700 dark:bg-violet-600 dark:hover:bg-violet-700 transition-transform cursor-pointer"
>
Upload Image & Convert
<ArrowRight className="h-5 w-5" />
</Button>
</Link>
</motion.div>
</motion.div>
</motion.div>
</div>
</section>
);
};

export default HeroSection;
5 changes: 2 additions & 3 deletions components/NavbarComponents/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ export default function Navbar() {
className="text-lg font-bold text-slate-800 dark:text-neutral-100"
>
SVG{" "}
<span className="text-emerald-600 dark:text-violet-500">
From Img
</span>
<span className="text-emerald-600 dark:text-violet-500">From</span>{" "}
Img
</Link>
</div>

Expand Down
60 changes: 60 additions & 0 deletions components/ui/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";

import { cn } from "@/lib/utils";

const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);

function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
}) {
const Comp = asChild ? Slot : "button";

return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
}

export { Button, buttonVariants };
Loading