Skip to content

Commit

Permalink
new features
Browse files Browse the repository at this point in the history
  • Loading branch information
ogeobubu committed Oct 22, 2024
1 parent 8517c23 commit 6d8af3f
Show file tree
Hide file tree
Showing 28 changed files with 1,516 additions and 136 deletions.
3 changes: 3 additions & 0 deletions client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
<head>
<meta charset="UTF-8" />
<!-- <link rel="icon" type="image/svg+xml" href="/vite.svg" /> -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,300..900;1,300..900&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Leaf Classification System</title>
</head>
Expand Down
5 changes: 4 additions & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
"dependencies": {
"@headlessui/react": "^2.1.9",
"@heroicons/react": "^2.1.5",
"@tanstack/react-query": "^5.59.15",
"axios": "^1.7.7",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"react-dom": "^18.3.1",
"react-router-dom": "^6.27.0"
},
"devDependencies": {
"@eslint/js": "^9.9.0",
Expand Down
16 changes: 13 additions & 3 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Homepage from "./pages/homepage";
import Upload from "./pages/upload";

const App = () => {
return <div className="font-bold">Hello World</div>;
const App: React.FC = () => {
return (
<Router>
<Routes>
<Route path="/" element={<Homepage />} />
<Route path="/upload" element={<Upload />} />
</Routes>
</Router>
);
};

export default App;
export default App;
Binary file added client/src/assets/leaf-bg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 43 additions & 0 deletions client/src/components/button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from "react";
import { Link } from "react-router-dom";

interface ButtonProps {
children: React.ReactNode;
className?: string;
href?: string;
onClick?: () => void;
disabled?: boolean;
}

const Button: React.FC<ButtonProps> = ({
children,
className,
href,
onClick,
disabled
}) => {
return (
<>
{href ? (
<Link to={href}>
<button
className={`${className} bg-white cursor-pointer rounded-lg px-6 py-2 text-[#083708] hover:bg-white-400`}
disabled={disabled}
>
{children}
</button>
</Link>
) : (
<button
onClick={onClick}
disabled={disabled}
className={`${className} bg-white cursor-pointer rounded-lg px-6 py-2 text-[#083708] hover:bg-white-400`}
>
{children}
</button>
)}
</>
);
};

export default Button;
10 changes: 10 additions & 0 deletions client/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,14 @@
padding: 0;
margin: 0;
box-sizing: border-box;
font-family: "Rubik", sans-serif;
font-optical-sizing: auto;
font-weight: 400;
font-style: normal;
}

.bg-image {
background-image: url('${bgImage}');
background-size: cover;
background-position: center;
}
5 changes: 5 additions & 0 deletions client/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { StrictMode } from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createRoot } from 'react-dom/client'
import App from './App.tsx'
import './index.css'

const queryClient = new QueryClient();

createRoot(document.getElementById('root')!).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</StrictMode>,
)
39 changes: 39 additions & 0 deletions client/src/pages/homepage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from "react";
import Button from "../../components/button";
import bgImage from "../../assets/leaf-bg.png";

const Homepage = () => {
return (
<div
style={{
backgroundImage: `url(${bgImage})`,
backgroundSize: "cover",
backgroundPosition: "center",
}}
className="h-[100vh]"
>
<div className="overlay absolute top-0 left-0 w-full h-full bg-black opacity-50"></div>
<div className="above-overlay absolute top-0 left-0 w-full h-full z-10">
<div className="flex flex-col justify-center items-center p-4 md:max-w-4xl m-auto h-[100%]">
<h1 className="text-4xl text-white my-8">
Leaf Identification System
</h1>
<p className="text-white text-2xl">
<strong className="text-4xl">Welcome</strong> to our leaf
identification system. This tool is designed to help you easily
identify plants based on their leaves. Simply upload an image of a
leaf, and our advanced algorithms will analyze its unique features
to provide accurate results
</p>
<div className="flex md:flex-row flex-col gap-3 justify-between items-center w-full mt-8">
<Button className="w-[100%]" href="/upload">Identify Leaf</Button>
<Button>Learn More</Button>
<Button>About Us</Button>
</div>
</div>
</div>
</div>
);
};

export default Homepage;
172 changes: 172 additions & 0 deletions client/src/pages/upload/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import React, { useCallback, useState } from "react";
import axios from "axios";
import { useMutation } from "@tanstack/react-query";
import Button from "../../components/button";
import bgImage from "../../assets/leaf-bg.png";
import { LeafResponse } from "../../typings";

const uploadImage = async (file: File): Promise<LeafResponse> => {
const formData = new FormData();
formData.append("image", file);

const response = await axios.post(
`http://localhost:5000/api/images/upload`,
formData,
{
headers: {
"Content-Type": "multipart/form-data",
},
}
);

return response.data;
};

const ImageUpload: React.FC = () => {
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [modalVisible, setModalVisible] = useState(false);
const [classificationResult, setClassificationResult] =
useState<LeafResponse | null>(null);

const mutation = useMutation<LeafResponse, Error, File>({
mutationFn: uploadImage,
onSuccess: (data) => {
console.log("Upload success:", data);
setClassificationResult(data);
setModalVisible(true);
},
onError: (error) => {
console.error("Upload error:", error);
},
});

const handleFiles = useCallback((files: FileList | null) => {
if (files && files.length > 0) {
setSelectedFile(files[0]);
}
}, []);

const handleUpload = () => {
if (selectedFile) {
mutation.mutate(selectedFile);
}
};

const handleDragOver = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
};

const handleDrop = (e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
handleFiles(e.dataTransfer.files);
};

return (
<div
style={{
backgroundImage: `url(${bgImage})`,
backgroundSize: "cover",
backgroundPosition: "center",
}}
className="h-[100vh]"
>
<div className="overlay absolute top-0 left-0 w-full h-full bg-black opacity-50"></div>
<div className="above-overlay absolute top-0 left-0 w-full h-full z-10">
<div className="flex flex-col justify-center items-center p-4 md:max-w-4xl m-auto h-[100%]">
<Button href="/" className="absolute top-3 left-3">
Go Back
</Button>
<div
className="dropzone flex flex-col justify-center items-center border-4 border-dashed border-green-500 rounded-lg h-96 w-full md:w-[580px] bg-white shadow-md mt-16 md:mt-0"
onDragOver={handleDragOver}
onDrop={handleDrop}
>
<input
type="file"
id="fileElem"
multiple
accept="image/*"
onChange={(e) => handleFiles(e.target.files)}
className="hidden"
/>
<label
htmlFor="fileElem"
className="flex flex-col justify-center items-center h-full cursor-pointer"
>
<span className="text-gray-400 mb-2 text-center">
Drag and drop your image here or click to upload
</span>
<span className="text-gray-300">Only images are allowed</span>
</label>
{selectedFile && (
<div className="mt-2 text-gray-600">{`Selected file: ${selectedFile.name}`}</div>
)}
</div>
<Button
onClick={handleUpload}
disabled={!selectedFile || mutation.isLoading}
className="mt-4"
>
Upload Image
</Button>
{mutation.isLoading && (
<p className="mt-2 text-blue-500">Uploading...</p>
)}
{mutation.isError && (
<p className="mt-2 text-red-500">Error uploading image.</p>
)}
{mutation.isSuccess && (
<p className="mt-2 text-green-500">Upload successful!</p>
)}
<p className="mt-6 text-white text-center">
File must be JPEG, JPG or PNG and up to 40MB
</p>
</div>
</div>

{modalVisible && classificationResult && (
<div
className="fixed inset-0 flex items-center justify-center z-20 bg-black bg-opacity-75"
role="dialog"
aria-labelledby="modal-title"
aria-modal="true"
onKeyDown={(e) => e.key === "Escape" && setModalVisible(false)} // Close on Esc key
>
<div className="bg-white rounded-lg shadow-lg p-6 max-w-md w-full max-h-[80vh] overflow-y-auto">
<h2 id="modal-title" className="text-lg font-bold mb-4">
Leaf Classification Result
</h2>

{/* <p className="mb-2">
<strong>Species:</strong> {classificationResult.leaf.species}
</p> */}

<img
src={`${classificationResult.leaf.imageUrl}`}
alt="Leaf"
className="mb-2 w-full h-auto rounded"
/>

<p className="mb-2">
<strong>Features:</strong>
</p>
<ul className="list-disc list-inside mb-4">
<li>Length: {classificationResult.leaf.features.length}</li>
<li>Width: {classificationResult.leaf.features.width}</li>
<li>Color: {classificationResult.leaf.features.color}</li>
</ul>

<button
onClick={() => setModalVisible(false)}
className="bg-red-500 text-white px-8 py-2 rounded-md"
>
Close
</button>
</div>
</div>
)}
</div>
);
};

export default ImageUpload;
5 changes: 5 additions & 0 deletions client/src/types/axios.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare module "axios" {
import { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
const axios: AxiosInstance;
export default axios;
}
19 changes: 19 additions & 0 deletions client/src/typings/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export interface ButtonProps {
children: React.ReactNode;
className?: string;
}

export interface LeafResponse {
message: string;
leaf: {
_id: string;
species: string;
image: string;
features: {
length: number;
width: number;
color: string;
};
[key: string]: any;
};
}
Loading

0 comments on commit 6d8af3f

Please sign in to comment.