diff --git a/.gitattributes b/.gitattributes index fcadb2c..1e80435 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,20 @@ * text eol=lf +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.mov binary +*.mp4 binary +*.mp3 binary +*.flv binary +*.fla binary +*.swf binary +*.gz binary +*.zip binary +*.7z binary +*.ttf binary +*.eot binary +*.woff binary +*.pyc binary +*.pdf binary diff --git a/client/app/components/BackButton.js b/client/app/components/BackButton.js new file mode 100644 index 0000000..556c447 --- /dev/null +++ b/client/app/components/BackButton.js @@ -0,0 +1,9 @@ +import styles from '../components/BackButton.module.css'; + +export default function ArrowButton({ onClick, children }) { + return ( + + ); +} diff --git a/client/app/components/BackButton.module.css b/client/app/components/BackButton.module.css new file mode 100644 index 0000000..9abf72c --- /dev/null +++ b/client/app/components/BackButton.module.css @@ -0,0 +1,25 @@ +.backButton { + align-self: flex-start; + padding: 5px 10px; + margin: 10px; + border-radius: 8px; + border: none; + background-color: #c5f687; + color: white; + font-size: 16px; + cursor: pointer; + transition: background-color 0.3s; + + position: relative; + display: flex; + align-items: center; + } + +.backButton:hover { + background-color: #a5d667; +} + +.arrow { + display: inline-block; + font-size: 1.5em; +} \ No newline at end of file diff --git a/client/app/components/Camera.js b/client/app/components/Camera.js new file mode 100644 index 0000000..b24d843 --- /dev/null +++ b/client/app/components/Camera.js @@ -0,0 +1,116 @@ +import React, { useState, useEffect, useRef } from 'react'; +import styles from './Camera.module.css'; + +const Camera = () => { + const videoRef = useRef(null); + const canvasRef = useRef(null); + const [devices, setDevices] = useState([]); + const [selectedDeviceId, setSelectedDeviceId] = useState(''); + const [photo, setPhoto] = useState(''); + + useEffect(() => { + const getDevices = async () => { + const allDevices = await navigator.mediaDevices.enumerateDevices(); + const videoDevices = allDevices.filter(device => device.kind === 'videoinput'); + setDevices(videoDevices); + if (videoDevices.length > 0) { + setSelectedDeviceId(videoDevices[0].deviceId); + } + }; + + getDevices(); + }, []); + + const startCamera = async () => { + if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { + try { + const stream = await navigator.mediaDevices.getUserMedia({ + video: { deviceId: selectedDeviceId ? { exact: selectedDeviceId } : undefined } + }); + if (videoRef.current) { + videoRef.current.srcObject = stream; + videoRef.current.play(); + } + } catch (err) { + console.error('Error accessing the camera: ', err); + } + } + }; + + useEffect(() => { + if (selectedDeviceId) { + startCamera(); + } + + return () => { + if (videoRef.current && videoRef.current.srcObject) { + videoRef.current.srcObject.getTracks().forEach((track) => track.stop()); + } + }; + }, [selectedDeviceId]); + + const takePhoto = () => { + const width = videoRef.current.videoWidth; + const height = videoRef.current.videoHeight; + + const context = canvasRef.current.getContext('2d'); + canvasRef.current.width = width; + canvasRef.current.height = height; + context.drawImage(videoRef.current, 0, 0, width, height); + + const dataUrl = canvasRef.current.toDataURL('image/png'); + setPhoto(dataUrl); + }; + + const uploadPhoto = async () => { + const response = await fetch('/api/upload', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ image: photo }), + }); + + const result = await response.json(); + console.log(result); + }; + + const retakePhoto = () => { + setPhoto(''); + startCamera(); // Restart the camera when retaking a photo + }; + + return ( +
+ {!photo && ( +
+ +
+ )} + {photo && ( +
+ Captured +
+ + +
+
+ )} + +
+ ); +}; + +export default Camera; diff --git a/client/app/components/Camera.module.css b/client/app/components/Camera.module.css new file mode 100644 index 0000000..3b5efa3 --- /dev/null +++ b/client/app/components/Camera.module.css @@ -0,0 +1,57 @@ +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.wrapper { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.video { + width: 100%; + max-width: 80vw; + border: 1px solid #ccc; + border-radius: 8px; +} + +.select { + padding: 10px; + margin: 10px 0; + border-radius: 8px; + border: 1px solid #ccc; + font-size: 16px; +} + +.buttonWrapper { + display: flex; + align-items: center; + justify-content: center; +} + +.button { + padding: 10px 20px; + margin: 20px 10px; + border-radius: 8px; + border: none; + background-color: #c5f687; + color: white; + font-size: 16px; + cursor: pointer; + transition: background-color 0.3s; +} + +.button:hover { + background-color: #a5d667; +} + +.image { + width: 100%; + max-width: 600px; + border: 1px solid #ccc; + border-radius: 8px; +} diff --git a/client/app/components/ScanInsectPlant.module.css b/client/app/components/ScanInsectPlant.module.css new file mode 100644 index 0000000..5ca7f50 --- /dev/null +++ b/client/app/components/ScanInsectPlant.module.css @@ -0,0 +1,44 @@ +.container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100%; + background-color: #fff; + padding: 20px; + font-family: 'Inter'; + } + + .title { + font-size: 24px; + margin: 20px 0; + color: #B3E576; + } + + .imageContainer { + display: flex; + align-items: center; + justify-content: center; + width: 350px; + border: 1px solid #ccc; + border-radius: 10px; + } + + .selectedImage { + width: 100%; + height: auto; + } + + .placeholder { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + width: 100%; + color: #B3E576; + } + + .fileInput { + margin-top: 20px; + } + \ No newline at end of file diff --git a/client/app/components/temp b/client/app/components/temp deleted file mode 100644 index e69de29..0000000 diff --git a/client/app/images/insect.png b/client/app/images/insect.png index 6fb349c..cb50531 100644 Binary files a/client/app/images/insect.png and b/client/app/images/insect.png differ diff --git a/client/app/images/placeholder.png b/client/app/images/placeholder.png new file mode 100644 index 0000000..f878f8d Binary files /dev/null and b/client/app/images/placeholder.png differ diff --git a/client/app/images/pollination.png b/client/app/images/pollination.png index 6fb349c..8c71dbf 100644 Binary files a/client/app/images/pollination.png and b/client/app/images/pollination.png differ diff --git a/client/app/images/scan-icon.png b/client/app/images/scan-icon.png index 1364d2f..4437863 100644 Binary files a/client/app/images/scan-icon.png and b/client/app/images/scan-icon.png differ diff --git a/client/app/pages/api/upload.js b/client/app/pages/api/upload.js new file mode 100644 index 0000000..f63ebc3 --- /dev/null +++ b/client/app/pages/api/upload.js @@ -0,0 +1,28 @@ +// pages/api/upload.js +import fs from 'fs'; +import path from 'path'; + +export default async (req, res) => { + if (req.method === 'POST') { + const { image } = req.body; + + // Create a buffer from the base64 image string + const buffer = Buffer.from(image.split(',')[1], 'base64'); + + // Define the path where the image will be saved + const uploadDir = path.join(process.cwd(), 'public', 'uploads'); + const filePath = path.join(uploadDir, `${Date.now()}.png`); + + // Ensure the uploads directory exists + if (!fs.existsSync(uploadDir)) { + fs.mkdirSync(uploadDir, { recursive: true }); + } + + // Write the buffer to a file + fs.writeFileSync(filePath, buffer); + + return res.status(200).json({ message: 'Upload successful', filePath }); + } + + return res.status(405).json({ message: 'Method not allowed' }); +}; diff --git a/client/app/saved-species/page.js b/client/app/saved-species/page.js new file mode 100644 index 0000000..03fbb3d --- /dev/null +++ b/client/app/saved-species/page.js @@ -0,0 +1,135 @@ +// app/saved-species/saved-species.js +'use client'; +import { useRouter } from 'next/navigation'; +import placeholder from '../images/pollination.png'; + +export default function SavedSpeciesPage() { + const router = useRouter(); + + const handleGoBackButton = () => { + console.log('Go Back Button clicked'); + router.push('/scan-species'); + }; + + return ( +
+ +

+ Saved List +

+
+
+ Plants +
+
+ Insects +
+
+
+
+ {/* Placeholder images for Plants */} + {Array.from({ length: 12 }).map((_, index) => ( +
+
+ ))} +
+
+ {/* Placeholder images for Insects */} +
+ Insect +
+ {Array.from({ length: 11 }).map((_, index) => ( +
+
+ ))} +
+
+
+ ); +} diff --git a/client/app/scan-insect/page.js b/client/app/scan-insect/page.js new file mode 100644 index 0000000..15748c8 --- /dev/null +++ b/client/app/scan-insect/page.js @@ -0,0 +1,51 @@ +'use client'; +import { useRouter } from 'next/navigation'; +import Image from 'next/image'; +import scanIcon from '../images/scan-icon.png'; +import Camera from '../components/Camera'; +import { useState } from 'react'; +import styles from '../components/ScanInsectPlant.module.css'; +import BackButton from '../components/BackButton'; + +export default function ScanInsectPage() { + const router = useRouter(); + const [selectedImage, setSelectedImage] = useState(null); + + const handleFileChange = (event) => { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = () => { + setSelectedImage(reader.result); + }; + reader.readAsDataURL(file); + } + }; + + const handleBack = () => { + router.push('/scan-species'); + }; + + return ( +
+ +

Take Photo or Choose Existing Image

+
+ {selectedImage ? ( + Selected + ) : ( +
+ +
+ )} +
+ +
+ ); +} diff --git a/client/app/scan-plant/page.js b/client/app/scan-plant/page.js new file mode 100644 index 0000000..15748c8 --- /dev/null +++ b/client/app/scan-plant/page.js @@ -0,0 +1,51 @@ +'use client'; +import { useRouter } from 'next/navigation'; +import Image from 'next/image'; +import scanIcon from '../images/scan-icon.png'; +import Camera from '../components/Camera'; +import { useState } from 'react'; +import styles from '../components/ScanInsectPlant.module.css'; +import BackButton from '../components/BackButton'; + +export default function ScanInsectPage() { + const router = useRouter(); + const [selectedImage, setSelectedImage] = useState(null); + + const handleFileChange = (event) => { + const file = event.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onload = () => { + setSelectedImage(reader.result); + }; + reader.readAsDataURL(file); + } + }; + + const handleBack = () => { + router.push('/scan-species'); + }; + + return ( +
+ +

Take Photo or Choose Existing Image

+
+ {selectedImage ? ( + Selected + ) : ( +
+ +
+ )} +
+ +
+ ); +} diff --git a/client/app/scan-species/page.js b/client/app/scan-species/page.js index 9389e87..7e35fde 100644 --- a/client/app/scan-species/page.js +++ b/client/app/scan-species/page.js @@ -8,14 +8,14 @@ export default function ScanSpeciesPage() { const router = useRouter(); const handleScanPlant = () => { - // Add your scan plant logic here - console.log('Scan a Plant clicked'); + console.log('Scan an Insect clicked'); + router.push('/scan-plant') }; const handleScanInsect = () => { // Add your scan insect logic here console.log('Scan an Insect clicked'); - router.push('/species-information/insect-information') + router.push('/scan-insect') }; const handleSavedSpecies = () => { diff --git a/client/package-lock.json b/client/package-lock.json index 0060c03..3996380 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -14,7 +14,7 @@ }, "devDependencies": { "eslint": "^8", - "eslint-config-next": "14.2.4" + "eslint-config-next": "14.2.5" } }, "node_modules/@babel/runtime": { @@ -168,9 +168,9 @@ "integrity": "sha512-3EtkY5VDkuV2+lNmKlbkibIJxcO4oIHEhBWne6PaAp+76J9KoSsGvNikp6ivzAT8dhhBMYrm6op2pS1ApG0Hzg==" }, "node_modules/@next/eslint-plugin-next": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.4.tgz", - "integrity": "sha512-svSFxW9f3xDaZA3idQmlFw7SusOuWTpDTAeBlO3AEPDltrraV+lqs7mAc6A27YdnpQVVIA3sODqUAAHdWhVWsA==", + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.5.tgz", + "integrity": "sha512-LY3btOpPh+OTIpviNojDpUdIbHW9j0JBYBjsIp8IxtDFfYFyORvw3yNq6N231FVqQA7n7lwaf7xHbVJlA1ED7g==", "dev": true, "dependencies": { "glob": "10.3.10" @@ -1344,12 +1344,12 @@ } }, "node_modules/eslint-config-next": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.4.tgz", - "integrity": "sha512-Qr0wMgG9m6m4uYy2jrYJmyuNlYZzPRQq5Kvb9IDlYwn+7yq6W6sfMNFgb+9guM1KYwuIo6TIaiFhZJ6SnQ/Efw==", + "version": "14.2.5", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.5.tgz", + "integrity": "sha512-zogs9zlOiZ7ka+wgUnmcM0KBEDjo4Jis7kxN1jvC0N4wynQ2MIx/KBkg4mVF63J5EK4W0QMCn7xO3vNisjaAoA==", "dev": true, "dependencies": { - "@next/eslint-plugin-next": "14.2.4", + "@next/eslint-plugin-next": "14.2.5", "@rushstack/eslint-patch": "^1.3.3", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0", "eslint-import-resolver-node": "^0.3.6", @@ -1974,9 +1974,9 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -2741,13 +2741,10 @@ } }, "node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/merge2": { "version": "1.4.1", diff --git a/client/package.json b/client/package.json index b34c906..0298dfd 100644 --- a/client/package.json +++ b/client/package.json @@ -15,6 +15,6 @@ }, "devDependencies": { "eslint": "^8", - "eslint-config-next": "14.2.4" + "eslint-config-next": "14.2.5" } }