diff --git a/app/(app)/home/page.tsx b/app/(app)/home/page.tsx index 96c5535..9f06840 100644 --- a/app/(app)/home/page.tsx +++ b/app/(app)/home/page.tsx @@ -1,9 +1,68 @@ -import React from 'react' +"use client"; +import VideoCard from "@/components/videoCard"; +import { Video } from "@/types/interfaces"; +import axios from "axios"; +import React, { useCallback, useEffect, useState } from "react"; const Home = () => { + const [videos, setVideos] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const fetchVideos = useCallback(async () => { + try { + const response = await axios.get("/api/videos"); + if (Array.isArray(response.data)) { + setVideos(response.data); + } else { + throw new Error("Unexpected response Format"); + } + } catch (error) { + console.log(error); + setError("Failed to fetch videos"); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchVideos(); + }, [fetchVideos]); + + const handleDownload = useCallback(async (url: string, title: string) => { + const link = document.createElement("a"); + link.href = url; + link.setAttribute("download", `${title}.mp4`); + link.setAttribute("target", "_blank"); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + }, []); + + if (loading) { + return
Loading...
; + } + return ( -
Home
- ) -} +
+

Videos

+ {videos.length === 0 ? ( +
+ No Videos available +
+ ) : ( +
+ {videos.map((video) => ( + + ))} +
+ )} +
+ ); +}; -export default Home \ No newline at end of file +export default Home; diff --git a/app/(app)/layout.tsx b/app/(app)/layout.tsx new file mode 100644 index 0000000..d2ea4ab --- /dev/null +++ b/app/(app)/layout.tsx @@ -0,0 +1,122 @@ +"use client" +import { useClerk, useUser } from "@clerk/nextjs"; +import { ImageIcon,LogOutIcon, MenuIcon } from "lucide-react"; +import Image from "next/image"; +import Link from "next/link"; +import { usePathname, useRouter } from "next/navigation"; +import React, { useState } from "react"; + +import { sideBarItems } from "@/utils/constants"; + +export default function AppLayout({ + children, +}: Readonly<{ children: React.ReactNode }>) { + const [sidebarOpen, setSidebarOpen] = useState(false); + const pathName = usePathname(); + const router = useRouter(); + const { signOut } = useClerk(); + const { user } = useUser(); + + const handleLogoClick = () => { + router.push("/home"); + }; + + const handleSignOut = async () => { + return await signOut; + }; + + return ( +
+ setSidebarOpen(!sidebarOpen)} + /> +
+
+
+
+ +
+
+ +
+ Cloudinary Showcase +
+ +
+
+ {user && ( + <> +
+
+ { +
+
+ + {user.username || user.emailAddresses[0].emailAddress} + + + + )} +
+
+
+
+
+ {children} +
+
+
+
+ + +
+
+ ); +} diff --git a/app/(app)/social/page.tsx b/app/(app)/social/page.tsx index 9cd6c76..389c4bb 100644 --- a/app/(app)/social/page.tsx +++ b/app/(app)/social/page.tsx @@ -1,9 +1,154 @@ -import React from 'react' +"use client"; + +import React, { useEffect, useRef, useState } from "react"; +import { CldImage } from "next-cloudinary"; +import { socialFormats } from "@/utils/constants"; + +type SocialFormat = keyof typeof socialFormats; + +export default function Soical() { + const [uploadImage, setUploadImage] = useState(null); + const [format, setFormat] = useState("Instagram Square (1:1)"); + const [isUploading, setIsUploading] = useState(false); + const [isTransforming, setIsTransforming] = useState(false); + const imgRef = useRef(null); + + useEffect(() => { + if (uploadImage) { + setIsTransforming(true) + } + }, [format, uploadImage]); + + const handleFileUpload = async ( + event: React.ChangeEvent, + ) => { + const file = event.target.files?.[0]; + if (!file) return; + + setIsUploading(true); + const formData = new FormData(); + formData.append("file", file); + + try { + const response = await fetch("api/upload-image", { + method: "POST", + body: formData, + }); + + if (!response.ok) throw new Error("Failed to upload image"); + + const data = await response.json(); + setUploadImage(data?.publicId); + } catch (error) { + console.error(error); + alert("Failed to upload image"); + } finally { + setIsUploading(false); + } + }; + + const handleDownload = async () => { + if (!imgRef.current) return; + + await fetch(imgRef.current.src) + .then((response) => response.blob()) + .then((blob) => { + const url = window.URL.createObjectURL(blob); + const link = document.createElement("a"); + link.href = url; + link.download = `${format.replace(/\s+/g, "_").toLowerCase()}.png`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + window.URL.revokeObjectURL(url); + }); + }; -const Social = () => { return ( -
Social
- ) -} + <> +
+
+

+ Soical Media Image Creator +

+ +
+
+

Upload an Image

+
+ + +
+ + {isUploading && ( +
+ +
+ )} -export default Social \ No newline at end of file + {uploadImage && ( +
+

Select social Media format

+
+ +
+ +
+

Preview:

+
+ {isTransforming && ( +
+ +
+ )} + + setIsTransforming(false)} + /> +
+
+ +
+ +
+
+ )} +
+
+
+
+ + ); +} diff --git a/app/api/videos/route.ts b/app/api/videos/route.ts index f00bd5f..2fd2b40 100644 --- a/app/api/videos/route.ts +++ b/app/api/videos/route.ts @@ -12,6 +12,7 @@ export async function GET(req: NextRequest) { return NextResponse.json( { error: "Something went wrong" }, { status: 500 }, + {message: `${error}`} ); } finally { await prisma.$disconnect(); diff --git a/app/layout.tsx b/app/layout.tsx index 11c0211..92c0f31 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,10 +1,10 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono, Roboto } from "next/font/google"; -import { ClerkProvider} from "@clerk/nextjs"; +import { ClerkProvider } from "@clerk/nextjs"; import "./globals.css"; import { cn } from "@/lib/utils"; -const roboto = Roboto({subsets:['latin'],variable:'--font-sans'}); +const roboto = Roboto({ subsets: ["latin"], variable: "--font-sans" }); const geistSans = Geist({ variable: "--font-geist-sans", @@ -27,15 +27,20 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - - - - {children} - - - + + + {children} + + ); } diff --git a/app/page.tsx b/app/page.tsx index ede5215..6ff5373 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,154 +1,3 @@ -"use client"; - -import React, { useEffect, useRef, useState } from "react"; -import { CldImage } from "next-cloudinary"; -import { socialFormats } from "@/utils/constants"; - -type SocialFormat = keyof typeof socialFormats; - export default function Home() { - const [uploadImage, setUploadImage] = useState(null); - const [format, setFormat] = useState("Instagram Square (1:1)"); - const [isUploading, setIsUploading] = useState(false); - const [isTransforming, setIsTransforming] = useState(false); - const imgRef = useRef(null); - - useEffect(() => { - if (uploadImage) { - setIsTransforming(true) - } - }, [format, uploadImage]); - - const handleFileUpload = async ( - event: React.ChangeEvent, - ) => { - const file = event.target.files?.[0]; - if (!file) return; - - setIsUploading(true); - const formData = new FormData(); - formData.append("file", file); - - try { - const response = await fetch("api/upload-image", { - method: "POST", - body: formData, - }); - - if (!response.ok) throw new Error("Failed to upload image"); - - const data = await response.json(); - setUploadImage(data?.publicId); - } catch (error) { - console.error(error); - alert("Failed to upload image"); - } finally { - setIsUploading(false); - } - }; - - const handleDownload = async () => { - if (!imgRef.current) return; - - await fetch(imgRef.current.src) - .then((response) => response.blob()) - .then((blob) => { - const url = window.URL.createObjectURL(blob); - const link = document.createElement("a"); - link.href = url; - link.download = `${format.replace(/\s+/g, "_").toLowerCase()}.png`; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - window.URL.revokeObjectURL(url); - }); - }; - - return ( - <> -
-
-

- Soical Media Image Creator -

- -
-
-

Upload an Image

-
- - -
- - {isUploading && ( -
- -
- )} - - {uploadImage && ( -
-

Select social Media format

-
- -
- -
-

Preview:

-
- {isTransforming && ( -
- -
- )} - - setIsTransforming(false)} - /> -
-
- -
- -
-
- )} -
-
-
-
- - ); + return <>; } diff --git a/components/videoCard.tsx b/components/videoCard.tsx index df61ea4..de441a5 100644 --- a/components/videoCard.tsx +++ b/components/videoCard.tsx @@ -6,6 +6,7 @@ import { filesize } from "filesize"; import { Clock, Download, FileDown, FileUp } from "lucide-react"; import { VideoProps } from "@/types/interfaces"; +import Image from "next/image"; dayjs.extend(relativeTime); @@ -90,7 +91,7 @@ const VideoCard: React.FC = ({ video, onDownload }) => { /> ) ) : ( - {video.Title} = ({ video, onDownload }) => { Compression: {" "} {compressionPercentage}% - + +
+
diff --git a/types/interfaces.ts b/types/interfaces.ts index 550026e..a058c3d 100644 --- a/types/interfaces.ts +++ b/types/interfaces.ts @@ -8,12 +8,23 @@ export interface CloudinaryUploadResult { } export interface formData { - file: File | null; - title: string; - description: string; + file: File | null; + title: string; + description: string; } export interface VideoProps { video: Video; - onDownload: (url:string,title:string) => void; -} \ No newline at end of file + onDownload: (url: string, title: string) => void; +} + +export interface Video { + id: string; + title: string; + description: string; + publicId: string; + originalSize: number; + commpressedSize: number; + duration: number; + createdAt: Date; +} diff --git a/utils/constants.ts b/utils/constants.ts index bf1a4b9..4372813 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -1,4 +1,5 @@ import { UploadApiOptions } from "cloudinary"; +import { LayoutDashboardIcon, Share2Icon, UploadIcon } from "lucide-react"; export const imageOptions: UploadApiOptions = { folder: "learningSaas/images" }; @@ -20,3 +21,9 @@ export const socialFormats = { "Twtter Header (3:1)": { width: 1500, height: 500, aspectRatio: "3:1" }, "Facebook Cover (205:78)": { width: 820, height: 312, aspectRatio: "205:78" }, }; + +export const sideBarItems = [ + { href: "/home", icon: LayoutDashboardIcon, label: "Home Page" }, + { href: "/social", icon: Share2Icon, label: "Soical Page" }, + { href: "/uploadvideo", icon: UploadIcon, label: "Video Upload" }, +];