diff --git a/app/actions/getImageId.ts b/app/actions/getImageId.ts new file mode 100644 index 0000000..bea697c --- /dev/null +++ b/app/actions/getImageId.ts @@ -0,0 +1,28 @@ + +"use server" +import { prisma } from "@/lib/prisma"; +import { auth } from "@/app/(auth)/auth"; +export async function getImageID(imageUrl: string) { + const Image = await prisma.image.findUnique({ + where: { + imageUrl: imageUrl + } + }); + + return { + status: "success", + message: "Organization created successfully", + data: Image.id + }; +} + +export async function getImages() { + const images = await prisma.image.findMany({ + }); + + return { + status: "success", + message: "Organization created successfully", + data: images + }; +} diff --git a/app/actions/orgActions.ts b/app/actions/orgActions.ts index 80ef80c..6aacb73 100644 --- a/app/actions/orgActions.ts +++ b/app/actions/orgActions.ts @@ -3,11 +3,6 @@ import { auth } from "@/app/(auth)/auth"; import { CreateOrganizationType } from "@/lib/validation/schema"; export async function createOrg(body: CreateOrganizationType) { - const session = await auth(); - const user = session?.user; - if (!user) { - //do something - } const org = await prisma.organization.create({ data: { name: body.name, diff --git a/app/actions/proccessImage.ts b/app/actions/proccessImage.ts index f1095b3..a81aa5b 100644 --- a/app/actions/proccessImage.ts +++ b/app/actions/proccessImage.ts @@ -1,3 +1,4 @@ +"use server" import { prisma } from "@/lib/prisma"; import { auth } from "@/app/(auth)/auth"; import { processImage } from "@/utils/ml-actions/proccessImage"; @@ -26,12 +27,11 @@ export async function proccessImageAndSave(imageId: string) { } } - const {processedImageUrl, analysis} = await processImage(imageUrl, imageId, user.id, image.name); + const {processedImageUrl} = await processImage(imageUrl, imageId, user.id, image.name); return { status: "success", message: "Image proccessed successfully", proccessImage: processedImageUrl, - analysis: analysis }; } \ No newline at end of file diff --git a/app/components/mainpage.tsx b/app/components/mainpage.tsx index d0f288a..2238df5 100644 --- a/app/components/mainpage.tsx +++ b/app/components/mainpage.tsx @@ -9,7 +9,7 @@ export default function Home() { const [showButton, setShowButton] = useState(false); const handleSignIn = async () => { try { - await Signin(); // ✅ Redirects to /upload after signing in + await Signin(); } catch (error) { console.error("Error during sign-in:", error); } finally { @@ -17,8 +17,8 @@ export default function Home() { }; useEffect(() => { - setTimeout(() => setAnimateText(true), 500); // Delay animation start - setTimeout(() => setShowButton(true), 2000); // Show button after text animation + setTimeout(() => setAnimateText(true), 500); + setTimeout(() => setShowButton(true), 2000); }, []); return ( @@ -29,18 +29,14 @@ export default function Home() { fill className="object-cover opacity-50" /> - - {/* Animated Text */} - The Postmen + SORT IQ - - {/* Animated Button */} {showButton && ( { + const [isExpanded, setIsExpanded] = useState(false); + const [activeFeature, setActiveFeature] = useState(null); + const [isMobile, setIsMobile] = useState(false); + const sidebarRef = useRef(null); + + // Handle screen resize + useEffect(() => { + const handleResize = () => { + setIsMobile(window.innerWidth < 768); + if (window.innerWidth < 768) { + setIsExpanded(false); + } + }; + + handleResize(); + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); + }, []); + + const handleClickOutside = (event: MouseEvent) => { + if ( + sidebarRef.current && + !sidebarRef.current.contains(event.target as Node) + ) { + setIsExpanded(false); + } + }; + + useEffect(() => { + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [sidebarRef]); + + const features = [ + { + name: "Live Video Stream", + icon: "/feat1.svg", + component: , + }, + { + name: "History ", + icon: "/feat2.svg", + component:
History
, + }, + { + name: "Analytics", + icon: "/feat3.svg", + component:
Analytics
, + }, + { + name: "Organization", + icon: "/feat4.svg", + component:
Organization
, + }, + { + name: "ESG Reports", + icon: "/feat5.svg", + component:
ESG Reports
, + }, + { + name: "Custom Reports ", + icon: "/feat6.svg", + component:
Custom Reports
, + }, + { + name: "Performance Reports", + icon: "/feat7.svg", + component:
Performance Reports
, + }, + ]; + + return ( +
+ CentralHack +
+ Sort IQ +
+
+ {/* Sidebar */} +
+ {isExpanded && !isMobile ? ( +
+
+ expand setIsExpanded(!isExpanded)} + /> +
+ Features +
+
+
+ {features.map((feature, index) => ( +
setActiveFeature(index)} + > + {feature.name} +
{feature.name}
+
+ ))} +
+
+ ) : ( +
+ {!isMobile && ( + expand setIsExpanded(!isExpanded)} + /> + )} + {features.map((feature, index) => ( +
setActiveFeature(index)} + > + {feature.name} + {isMobile && ( +
{feature.name}
+ )} +
+ ))} +
+ )} +
+ + {/* Main Content */} +
+ {activeFeature !== null && features[activeFeature].component} +
+
+
+ ); +}; + +export default Organization; diff --git a/app/components/upload.tsx b/app/components/upload.tsx new file mode 100644 index 0000000..83b6eda --- /dev/null +++ b/app/components/upload.tsx @@ -0,0 +1,135 @@ +"use client"; + +import { useState } from "react"; +import { proccessImageAndSave } from "../actions/proccessImage"; +export default function FileUploadTest() { + const [file, setFile] = useState(null); + const [uploading, setUploading] = useState(false); + const [uploadedUrl, setUploadedUrl] = useState(null); + const [error, setError] = useState(null); + const [image, setImage] = useState(null); + const handleFileChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files[0]) { + setFile(e.target.files[0]); + setError(null); + setUploadedUrl(null); + } + }; + + const handleUpload = async () => { + if (!file) { + setError("Please select a file first"); + return; + } + + setUploading(true); + setError(null); + + try { + const formData = new FormData(); + formData.append("file", file); + + const response = await fetch("/api/upload", { + method: "POST", + body: formData, + }); + + if (!response.ok) { + throw new Error("Upload failed"); + } + + const data = await response.json(); + const res = await proccessImageAndSave(data.image.id); + + setImage(res.proccessImage || null); + setUploadedUrl(data.url); + } catch (err) { + setError("Failed to upload file. Please try again."); + console.error("Upload error:", err); + } finally { + setUploading(false); + } + }; + + return ( +
+
+
+
+ + +
+ {file && ( +
+

+ Selected file: {file.name} +

+
+ )} + +
+ {error && ( +
+
{error}
+
+ )} + {uploadedUrl && ( +
+

+ File uploaded successfully! +

+ + {uploadedUrl} + + + {uploadedUrl && ( +
+ Uploaded preview +
+ )} +
+ )} + {file && file.type.startsWith("image/") && !uploadedUrl && ( +
+ Preview +
+ )} + {image && ( +
+ Preview +
+ )} +
+
+ ); +} diff --git a/app/getImage/page.tsx b/app/getImage/page.tsx new file mode 100644 index 0000000..5aecb99 --- /dev/null +++ b/app/getImage/page.tsx @@ -0,0 +1,47 @@ +"use client"; +import React, { useState, useEffect } from "react"; +import { getImages } from "../actions/getImageId"; + +interface ImageData { + processedImageUrl: string; +} + +const ImageList = () => { + const [images, setImages] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + fetchImages(); + }, []); + + const fetchImages = async () => { + try { + const images = await getImages(); + setImages(images.data); + } catch (err) { + console.error("Failed to fetch images:", err); + } finally { + setLoading(false); + } + }; + + return ( +
+ {loading ? ( +

Loading...

+ ) : ( +
    + {images.map((image, index) => + image.processedImageUrl ? ( +
  • + {`Image +
  • + ) : null + )} +
+ )} +
+ ); +}; + +export default ImageList; diff --git a/app/globals.css b/app/globals.css index 6b717ad..d42bbf5 100644 --- a/app/globals.css +++ b/app/globals.css @@ -19,3 +19,12 @@ body { background: var(--background); font-family: Arial, Helvetica, sans-serif; } + +@font-face { + font-family: 'deutschlander'; + src: url('../public/fonts/Deutschlander.otf') format('opentype'); +} + +.deutschlander { + font-family: 'deutschlander', Arial, Helvetica, sans-serif; +} \ No newline at end of file diff --git a/app/page.tsx b/app/page.tsx index be63791..c6e34a1 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2,9 +2,10 @@ import { useSession } from "next-auth/react"; import Home from "./components/mainpage"; +import Organization from "./components/organization"; export default function Page() { - const { data: session } = useSession(); // ✅ Get session from NextAuth + const { data: session } = useSession(); - return
{}
; + return
{session ? : }
; } diff --git a/app/signout/page.tsx b/app/signout/page.tsx new file mode 100644 index 0000000..21a4390 --- /dev/null +++ b/app/signout/page.tsx @@ -0,0 +1,24 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import SignOut from "../(auth)/authactions/signout"; + +export default function SignOutPage() { + const router = useRouter(); + + const handleSignOut = async () => { + await SignOut(); + router.push("/"); + }; + + return ( +
+ +
+ ); +} diff --git a/app/upload/page.tsx b/app/upload/page.tsx index b2dcfd3..0baa292 100644 --- a/app/upload/page.tsx +++ b/app/upload/page.tsx @@ -1,83 +1,136 @@ "use client"; -import { useState } from "react"; -import { revalidateUpload } from "../(auth)/authactions/revalidate"; -export default function UploadPage() { + +import { useState, useEffect } from "react"; +import { getImages } from "../actions/getImageId"; +import { console } from "inspector"; +export default function FileUploadTest() { const [file, setFile] = useState(null); const [uploading, setUploading] = useState(false); - const [progress, setProgress] = useState(0); - const [message, setMessage] = useState(""); - - const handleFileChange = (event: React.ChangeEvent) => { - if (event.target.files && event.target.files.length > 0) { - const selectedFile = event.target.files[0]; - - if (selectedFile.type !== "video/mp4") { - setMessage("Only .mp4 files are allowed."); - return; - } + const [uploadedUrl, setUploadedUrl] = useState(null); + const [error, setError] = useState(null); - setFile(selectedFile); - setMessage(""); + const handleFileChange = (e: React.ChangeEvent) => { + if (e.target.files && e.target.files[0]) { + setFile(e.target.files[0]); + setError(null); + setUploadedUrl(null); } }; const handleUpload = async () => { if (!file) { - setMessage("Please select a .mp4 file first."); + setError("Please select a file first"); return; } setUploading(true); - setMessage(""); - - const formData = new FormData(); - formData.append("file", file); + setError(null); try { - console.log("Uploading file..."); + const formData = new FormData(); + formData.append("file", file); + const response = await fetch("/api/upload", { method: "POST", body: formData, }); - console.log(response); - const result = await response.json(); - console.log(result); - await revalidateUpload(); - if (response.ok) { - setMessage(`Movie uploaded successfully: ${result.filename}`); - } else { - setMessage(`Upload failed: ${result.message}`); + + if (!response.ok) { + throw new Error("Upload failed"); } - } catch (error) { - setMessage("An error occurred while uploading."); + + const data = await response.json(); + setUploadedUrl(data.url); + } catch (err) { + setError("Failed to upload file. Please try again."); + console.error("Upload error:", err); } finally { setUploading(false); } }; + const [images, setImages] = useState([]); + + useEffect(() => { + const fetchImages = async () => { + const result = await getImages(); + console.log(result); + setImages(result.data || []); + }; + fetchImages(); + }, []); return ( -
-
-

Upload a Movie (.mp4)

- - - {progress > 0 && ( -

Upload Progress: {progress}%

+
+
+
+
+ + +
+ + {file && ( +
+

+ Selected file: {file.name} +

+
+ )} + + +
+ + {error && ( +
+
{error}
+
+ )} + + {uploadedUrl && ( +
+

+ File uploaded successfully! +

+ + {uploadedUrl} + +
)} - {message &&

{message}

} + {/* Image Gallery */} +
+

Uploaded Images

+
+ {images?.map((image, index) => ( +
+ {`Uploaded +
+ ))} +
+
); diff --git a/package-lock.json b/package-lock.json index 9724f39..299e825 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,11 +14,13 @@ "@types/multer": "^1.4.12", "formidable": "^3.5.2", "framer-motion": "^12.4.1", + "lucide-react": "^0.475.0", "multer": "^1.4.5-lts.1", "next": "15.1.6", "next-auth": "^5.0.0-beta.25", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "tls": "^0.0.1" }, "devDependencies": { "@eslint/eslintrc": "^3", @@ -4714,6 +4716,15 @@ "dev": true, "license": "ISC" }, + "node_modules/lucide-react": { + "version": "0.475.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.475.0.tgz", + "integrity": "sha512-NJzvVu1HwFVeZ+Gwq2q00KygM1aBhy/ZrhY9FsAgJtpB+E4R7uxRk9M2iKvHa6/vNxZydIB59htha4c2vvwvVg==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -6655,6 +6666,11 @@ "node": ">=0.8" } }, + "node_modules/tls": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/tls/-/tls-0.0.1.tgz", + "integrity": "sha512-GzHpG+hwupY8VMR6rYsnAhTHqT/97zT45PG8WD5eTT1lq+dFE0nN+1PYpsoBcHJgSmTz5ceK2Cv88IkPmIPOtQ==" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/package.json b/package.json index ca8a208..42543d8 100644 --- a/package.json +++ b/package.json @@ -15,11 +15,13 @@ "@types/multer": "^1.4.12", "formidable": "^3.5.2", "framer-motion": "^12.4.1", + "lucide-react": "^0.475.0", "multer": "^1.4.5-lts.1", "next": "15.1.6", "next-auth": "^5.0.0-beta.25", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "tls": "^0.0.1" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/prisma/migrations/20250208101922_render/migration.sql b/prisma/migrations/20250208101922_render/migration.sql new file mode 100644 index 0000000..a0c12bd --- /dev/null +++ b/prisma/migrations/20250208101922_render/migration.sql @@ -0,0 +1,2 @@ +-- DropIndex +DROP INDEX "Image_name_key"; diff --git a/public/expand.svg b/public/expand.svg new file mode 100644 index 0000000..382e635 --- /dev/null +++ b/public/expand.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/expanded.svg b/public/expanded.svg new file mode 100644 index 0000000..a2f09f1 --- /dev/null +++ b/public/expanded.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/feat1.svg b/public/feat1.svg new file mode 100644 index 0000000..638db82 --- /dev/null +++ b/public/feat1.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/public/feat2.svg b/public/feat2.svg new file mode 100644 index 0000000..4ee22bc --- /dev/null +++ b/public/feat2.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/feat3.svg b/public/feat3.svg new file mode 100644 index 0000000..a6f7883 --- /dev/null +++ b/public/feat3.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/feat4.svg b/public/feat4.svg new file mode 100644 index 0000000..bd1f548 --- /dev/null +++ b/public/feat4.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/feat5.svg b/public/feat5.svg new file mode 100644 index 0000000..99f99b6 --- /dev/null +++ b/public/feat5.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/public/feat6.svg b/public/feat6.svg new file mode 100644 index 0000000..907a058 --- /dev/null +++ b/public/feat6.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/public/feat7.svg b/public/feat7.svg new file mode 100644 index 0000000..3303317 --- /dev/null +++ b/public/feat7.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/fonts/Deutschlander.otf b/public/fonts/Deutschlander.otf new file mode 100644 index 0000000..2619127 Binary files /dev/null and b/public/fonts/Deutschlander.otf differ diff --git a/utils/ml-actions/proccessImage.ts b/utils/ml-actions/proccessImage.ts index ef33bcf..fdaca10 100644 --- a/utils/ml-actions/proccessImage.ts +++ b/utils/ml-actions/proccessImage.ts @@ -3,67 +3,66 @@ import { projectClient } from '@/lib/bucket-client'; export async function processImage(imageUrl: string, imageId: string, userId: string, imageName: string) { try { - // Send the image to ML server + const response = await fetch(`${process.env.ML_SERVER_URL}/analyze-waste`, { method: "POST", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ imageUrl }), + body: JSON.stringify({ image_url: imageUrl }), }); const data = await response.json(); + console.log(data); - // Convert base64 to buffer - const base64Image = data.visualisation.replace(/^data:image\/\w+;base64,/, ''); + + const base64Image = data.visualization.replace(/^data:image\/\w+;base64,/, ''); const buffer = Buffer.from(base64Image, 'base64'); - - // Upload processed image to bucket + const processedImageFileName = `${Date.now()}-${userId}-processed-${imageName}.png`; const processedImageUrl = await projectClient.uploadFile( buffer, processedImageFileName ); - // Save analysis results to database - const analysis = await prisma.wasteAnalysis.create({ - data: { - imageId, - totalWeightKg: data.total_stats.total_weight_kg, - wasteCoverage: data.total_stats.waste_coverage, - distributionEvenness: data.total_stats.distribution_evenness, - compositions: { - create: [ - { - type: 'RIGID_PLASTIC', - present: data.waste_composition.rigid_plastic.present, - pixelCount: data.waste_composition.rigid_plastic.pixel_count, - areaPercentage: data.waste_composition.rigid_plastic.area_percentage, - estimatedWeightKg: data.waste_composition.rigid_plastic.estimated_weight_kg, - }, - { - type: 'CARDBOARD', - present: data.waste_composition.cardboard.present, - pixelCount: data.waste_composition.cardboard.pixel_count, - areaPercentage: data.waste_composition.cardboard.area_percentage, - estimatedWeightKg: data.waste_composition.cardboard.estimated_weight_kg, - }, - { - type: 'METAL', - present: data.waste_composition.metal.present, - pixelCount: data.waste_composition.metal.pixel_count, - areaPercentage: data.waste_composition.metal.area_percentage, - estimatedWeightKg: data.waste_composition.metal.estimated_weight_kg, - }, - { - type: 'SOFT_PLASTIC', - present: data.waste_composition.soft_plastic.present, - pixelCount: data.waste_composition.soft_plastic.pixel_count, - areaPercentage: data.waste_composition.soft_plastic.area_percentage, - estimatedWeightKg: data.waste_composition.soft_plastic.estimated_weight_kg, - }, - ], - }, - }, - }); + // const analysis = await prisma.wasteAnalysis.create({ + // data: { + // imageId, + // totalWeightKg: data.total_stats.total_weight_kg, + // wasteCoverage: data.total_stats.waste_coverage, + // distributionEvenness: data.total_stats.distribution_evenness, + // compositions: { + // create: [ + // { + // type: 'RIGID_PLASTIC', + // present: data.waste_composition.rigid_plastic.present, + // pixelCount: data.waste_composition.rigid_plastic.pixel_count, + // areaPercentage: data.waste_composition.rigid_plastic.area_percentage, + // estimatedWeightKg: data.waste_composition.rigid_plastic.estimated_weight_kg, + // }, + // { + // type: 'CARDBOARD', + // present: data.waste_composition.cardboard.present, + // pixelCount: data.waste_composition.cardboard.pixel_count, + // areaPercentage: data.waste_composition.cardboard.area_percentage, + // estimatedWeightKg: data.waste_composition.cardboard.estimated_weight_kg, + // }, + // { + // type: 'METAL', + // present: data.waste_composition.metal.present, + // pixelCount: data.waste_composition.metal.pixel_count, + // areaPercentage: data.waste_composition.metal.area_percentage, + // estimatedWeightKg: data.waste_composition.metal.estimated_weight_kg, + // }, + // { + // type: 'SOFT_PLASTIC', + // present: data.waste_composition.soft_plastic.present, + // pixelCount: data.waste_composition.soft_plastic.pixel_count, + // areaPercentage: data.waste_composition.soft_plastic.area_percentage, + // estimatedWeightKg: data.waste_composition.soft_plastic.estimated_weight_kg, + // }, + // ], + // }, + // }, + // }); await prisma.image.update({ where: { id: imageId }, @@ -72,7 +71,6 @@ export async function processImage(imageUrl: string, imageId: string, userId: st return { processedImageUrl, - analysis }; } catch (error) { console.error("Error analyzing waste:", error);