diff --git a/.github/workflows/firebase-hosting-merge.yml b/.github/workflows/firebase-hosting-merge.yml
new file mode 100644
index 0000000..d73d55e
--- /dev/null
+++ b/.github/workflows/firebase-hosting-merge.yml
@@ -0,0 +1,20 @@
+# This file was auto-generated by the Firebase CLI
+# https://github.com/firebase/firebase-tools
+
+name: Deploy to Firebase Hosting on merge
+'on':
+ push:
+ branches:
+ - main
+jobs:
+ build_and_deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - run: npm ci && npm run build
+ - uses: FirebaseExtended/action-hosting-deploy@v0
+ with:
+ repoToken: '${{ secrets.GITHUB_TOKEN }}'
+ firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_PLANT_PAPA }}'
+ channelId: live
+ projectId: plant-papa
diff --git a/.github/workflows/firebase-hosting-pull-request.yml b/.github/workflows/firebase-hosting-pull-request.yml
new file mode 100644
index 0000000..bc40960
--- /dev/null
+++ b/.github/workflows/firebase-hosting-pull-request.yml
@@ -0,0 +1,17 @@
+# This file was auto-generated by the Firebase CLI
+# https://github.com/firebase/firebase-tools
+
+name: Deploy to Firebase Hosting on PR
+'on': pull_request
+jobs:
+ build_and_preview:
+ if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - run: npm ci && npm run build
+ - uses: FirebaseExtended/action-hosting-deploy@v0
+ with:
+ repoToken: '${{ secrets.GITHUB_TOKEN }}'
+ firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT_PLANT_PAPA }}'
+ projectId: plant-papa
diff --git a/firebase.json b/firebase.json
new file mode 100644
index 0000000..e782939
--- /dev/null
+++ b/firebase.json
@@ -0,0 +1,10 @@
+{
+ "hosting": {
+ "public": "public",
+ "ignore": [
+ "firebase.json",
+ "**/.*",
+ "**/node_modules/**"
+ ]
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index 1826e20..e655559 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,10 +13,14 @@
"@mui/material": "^5.15.0",
"@types/react-router-dom": "^5.3.3",
"axios": "^1.6.2",
+ "compressorjs": "^1.2.1",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"firebase": "^10.7.1",
+ "form-data": "^4.0.0",
+ "image-size": "^1.0.2",
+ "multer": "^1.4.5-lts.1",
"node-fetch": "^3.3.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@@ -2535,6 +2539,11 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/append-field": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
+ "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="
+ },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@@ -2590,6 +2599,11 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
+ "node_modules/blueimp-canvas-to-blob": {
+ "version": "3.29.0",
+ "resolved": "https://registry.npmjs.org/blueimp-canvas-to-blob/-/blueimp-canvas-to-blob-3.29.0.tgz",
+ "integrity": "sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg=="
+ },
"node_modules/body-parser": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
@@ -2648,6 +2662,22 @@
"node": ">=8"
}
},
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
+ },
+ "node_modules/busboy": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
+ "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==",
+ "dependencies": {
+ "streamsearch": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=10.16.0"
+ }
+ },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -2741,12 +2771,35 @@
"node": ">= 0.8"
}
},
+ "node_modules/compressorjs": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/compressorjs/-/compressorjs-1.2.1.tgz",
+ "integrity": "sha512-+geIjeRnPhQ+LLvvA7wxBQE5ddeLU7pJ3FsKFWirDw6veY3s9iLxAQEw7lXGHnhCJvBujEQWuNnGzZcvCvdkLQ==",
+ "dependencies": {
+ "blueimp-canvas-to-blob": "^3.29.0",
+ "is-blob": "^2.1.0"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
+ "node_modules/concat-stream": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+ "engines": [
+ "node >= 0.8"
+ ],
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.2.2",
+ "typedarray": "^0.0.6"
+ }
+ },
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -2784,6 +2837,11 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
},
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
+ },
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
@@ -3775,6 +3833,20 @@
"node": ">= 4"
}
},
+ "node_modules/image-size": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.2.tgz",
+ "integrity": "sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==",
+ "dependencies": {
+ "queue": "6.0.2"
+ },
+ "bin": {
+ "image-size": "bin/image-size.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -3827,6 +3899,17 @@
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
"integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="
},
+ "node_modules/is-blob": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-blob/-/is-blob-2.1.0.tgz",
+ "integrity": "sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-core-module": {
"version": "2.13.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
@@ -3885,6 +3968,11 @@
"node": ">=8"
}
},
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
+ },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -4097,12 +4185,48 @@
"node": "*"
}
},
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+ "dependencies": {
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
+ "node_modules/multer": {
+ "version": "1.4.5-lts.1",
+ "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz",
+ "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==",
+ "dependencies": {
+ "append-field": "^1.0.0",
+ "busboy": "^1.0.0",
+ "concat-stream": "^1.5.2",
+ "mkdirp": "^0.5.4",
+ "object-assign": "^4.1.1",
+ "type-is": "^1.6.4",
+ "xtend": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
@@ -4389,6 +4513,11 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+ },
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
@@ -4467,6 +4596,14 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/queue": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
+ "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
+ "dependencies": {
+ "inherits": "~2.0.3"
+ }
+ },
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -4582,6 +4719,25 @@
"react-dom": ">=16.6.0"
}
},
+ "node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/readable-stream/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
"node_modules/regenerator-runtime": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
@@ -4885,6 +5041,27 @@
"node": ">= 0.8"
}
},
+ "node_modules/streamsearch": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
+ "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/string_decoder/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@@ -5036,6 +5213,11 @@
"node": ">= 0.6"
}
},
+ "node_modules/typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
+ },
"node_modules/typescript": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
@@ -5082,6 +5264,11 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
+ },
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@@ -5219,6 +5406,14 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
diff --git a/package.json b/package.json
index 176fbfb..4b18fce 100644
--- a/package.json
+++ b/package.json
@@ -15,10 +15,14 @@
"@mui/material": "^5.15.0",
"@types/react-router-dom": "^5.3.3",
"axios": "^1.6.2",
+ "compressorjs": "^1.2.1",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"firebase": "^10.7.1",
+ "form-data": "^4.0.0",
+ "image-size": "^1.0.2",
+ "multer": "^1.4.5-lts.1",
"node-fetch": "^3.3.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
diff --git a/public/404.html b/public/404.html
new file mode 100644
index 0000000..829eda8
--- /dev/null
+++ b/public/404.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+ Page Not Found
+
+
+
+
+
+
404
+
Page Not Found
+
The specified file was not found on this website. Please check the URL for mistakes and try again.
+
Why am I seeing this?
+
This page was generated by the Firebase Command-Line Interface. To modify it, edit the 404.html
file in your project's configured public
directory.
+
+
+
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..e368b45
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,89 @@
+
+
+
+
+
+ Welcome to Firebase Hosting
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Welcome
+
Firebase Hosting Setup Complete
+
You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary!
+
Open Hosting Documentation
+
+ Firebase SDK Loading…
+
+
+
+
diff --git a/server.js b/server.js
index 56f9983..7642b2c 100644
--- a/server.js
+++ b/server.js
@@ -4,14 +4,21 @@ import express from 'express';
import cors from 'cors';
import axios from 'axios';
import process from 'process';
+import FormData from 'form-data';
+import multer from 'multer';
+import path from 'path';
+import fs from 'fs';
const app = express();
const port = 3000;
const TREFLE_API_TOKEN = process.env.TREFLE_API_KEY;
+const PLANTNET_API_KEY = process.env.REACT_APP_PLANTNET_API_KEY;
// Enable CORS
app.use(cors());
+app.use(express.json());
+app.use(express.urlencoded({ extended: true }));
// Define your proxy endpoint
app.get('/api/plants/search', async (req, res) => {
@@ -52,6 +59,58 @@ app.get('/api/plants/:plantId', async (req, res) => {
}
});
+
+
+const destination = "upload/images";
+
+const diskStorage = multer.diskStorage({
+ destination: destination,
+ filename: (req, file, cb) => {
+ return cb(null, `${file.fieldname}_${Date.now()}${path.extname(file.originalname)}`);
+ },
+});
+
+const upload = multer({
+ storage: diskStorage,
+ limits: {
+ fileSize: 1000000,
+ },
+});
+
+app.post('/api/plantnet/upload', upload.single("images"), async (req, res) => {
+ try {
+ const formData = new FormData();
+
+ console.log('Request Body:', req.body);
+ console.log('Request File:', req.file);
+ console.log('Form Data:', formData);
+
+
+ formData.append('organs', 'flower');
+ formData.append('images', fs.createReadStream(req.file.path));
+
+ if (!req.file) {
+ console.error('No file provided in the request');
+ return res.status(400).json({ error: 'No file provided' });
+ }
+
+ const project = 'all';
+
+ const { status, data } = await axios.post('https://my-api.plantnet.org/v2/identify/' + project + `?api-key=${PLANTNET_API_KEY}`, formData, {
+ headers: formData.getHeaders(),
+ });
+
+ console.log('status', status);
+ console.log('data', data);
+
+ console.log('Identification status: ', status);
+ res.json(data);
+ } catch (error) {
+ console.error('Server error uploading image:', error);
+ res.status(500).json({ error: 'Internal Server Error' });
+ }
+});
+
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});
\ No newline at end of file
diff --git a/src/components/compounds/PlantRecognition/PlantRecognition.css b/src/components/compounds/PlantRecognition/PlantRecognition.css
new file mode 100644
index 0000000..6aa2846
--- /dev/null
+++ b/src/components/compounds/PlantRecognition/PlantRecognition.css
@@ -0,0 +1,7 @@
+.flex-box {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ height: 100%;;
+}
\ No newline at end of file
diff --git a/src/components/compounds/PlantRecognition/PlantRecognition.tsx b/src/components/compounds/PlantRecognition/PlantRecognition.tsx
new file mode 100644
index 0000000..ac6fa19
--- /dev/null
+++ b/src/components/compounds/PlantRecognition/PlantRecognition.tsx
@@ -0,0 +1,90 @@
+import React, { useState, ChangeEvent, useEffect } from 'react';
+import Compressor from 'compressorjs';
+import axios from 'axios';
+import './PlantRecognition.css'
+import Container from '../../elements/container/Container';
+
+const ImageUpload: React.FC = () => {
+ const [selectedFile, setSelectedFile] = useState(null);
+ const [bestMatch, setBestMatch] = useState(null);
+ const [loading, setLoading] = useState(false);
+ const [uploadedImage, setUploadedImage] = useState(null);
+
+ useEffect(() => {
+ console.log(uploadedImage)
+ if (selectedFile) {
+ const reader = new FileReader();
+ reader.onloadend = () => {
+ setUploadedImage(reader.result as string);
+ };
+ reader.readAsDataURL(selectedFile);
+ }
+ }, [selectedFile]);
+
+ const handleFileChange = (event: ChangeEvent) => {
+ if (event.target.files && event.target.files.length > 0) {
+ setSelectedFile(event.target.files[0]);
+ }
+ };
+
+ const handleUpload = async () => {
+ try {
+ setLoading(true);
+
+ // Compress the selected image with specified dimensions and quality
+ const compressedImage = await compressImage(selectedFile, 800, 1280, 0.9);
+
+ // Create FormData with the compressed image
+ const formData = new FormData();
+ formData.append('organs', 'flower');
+ formData.append('images', compressedImage, 'compressed.jpg');
+
+ const { status, data } = await axios.post('http://localhost:3000/api/plantnet/upload', formData);
+
+ console.log('Response status:', status);
+ console.log('Response data:', data);
+
+ setBestMatch(data.bestMatch);
+ } catch (error) {
+ console.error('Frontend error uploading image:', error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const compressImage = (file: File | null, smallSide: number, largeSide: number, quality: number): Promise => {
+ return new Promise((resolve, reject) => {
+ if (!file) {
+ reject(new Error('No file provided'));
+ return;
+ }
+
+ new Compressor(file, {
+ maxWidth: largeSide,
+ maxHeight: smallSide,
+ quality,
+ success(result) {
+ resolve(result);
+ },
+ error(err) {
+ reject(err);
+ },
+ });
+ });
+ };
+
+ return (
+
+
+
+
+ {loading &&
Uploading and processing image...
}
+ {bestMatch &&
{bestMatch}
}
+ {uploadedImage &&
}
+
+
+
+ );
+};
+
+export default ImageUpload;
diff --git a/src/components/compounds/addPlant/AddPlant.css b/src/components/compounds/addPlant/AddPlant.css
new file mode 100644
index 0000000..c9f93bf
--- /dev/null
+++ b/src/components/compounds/addPlant/AddPlant.css
@@ -0,0 +1,4 @@
+.add-plant {
+ display: flex;
+ flex-direction: column;
+}
\ No newline at end of file
diff --git a/src/components/compounds/addPlant/AddPlant.tsx b/src/components/compounds/addPlant/AddPlant.tsx
new file mode 100644
index 0000000..4d097cb
--- /dev/null
+++ b/src/components/compounds/addPlant/AddPlant.tsx
@@ -0,0 +1,71 @@
+import React, { useState } from 'react';
+import Container from '../../elements/container/Container';
+import Button from '../../elements/button/Button';
+
+import { doc, setDoc } from "firebase/firestore";
+import { auth, db } from '../../../config/firebase.ts';
+
+import { useNavigate } from 'react-router-dom';
+
+
+const AddPlant: React.FC = () => {
+ const [name, setName] = useState('');
+ const [description, setDescription] = useState('');
+ const [picture, setPicture] = useState('');
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const [scientificName, setScientificName] = useState('');
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const [id, setId] = useState('');
+
+ const navigate = useNavigate();
+
+ const handleClick = async () => {
+ try{
+ setPicture("https://images.thdstatic.com/productImages/5ed4957b-8edf-4b0f-b4fb-3d6f48a78caa/svn/green-art-prints-julnpp02nfpfl12-64_600.jpg");
+ const userId = auth?.currentUser?.uid as string;
+ const uniquePlantId = new Date().getTime().toString();
+
+ await setDoc(doc(db, "myGarden", userId), {
+ [uniquePlantId]: {
+ plantName: name,
+ scientificName: scientificName,
+ picture: picture,
+ description: description,
+ id: id,
+ uniquePlantId: uniquePlantId,
+ lastWatered: "",
+ notes: []
+ }
+ }, {merge: true});
+
+ navigate('/my-garden');
+ } catch(error){
+ console.error(error);
+ }
+ }
+
+ return (
+
+
+
+ setName(e.target.value)}
+ />
+
+
+
+ );
+};
+
+export default AddPlant;
diff --git a/src/components/compounds/header/Header.css b/src/components/compounds/header/Header.css
index a45df70..92cd157 100644
--- a/src/components/compounds/header/Header.css
+++ b/src/components/compounds/header/Header.css
@@ -22,3 +22,18 @@
padding-right: 1.5%;
}
+@media screen and (max-width: 650px) {
+ .row {
+ flex-direction: column;
+ }
+
+ .col1 {
+ width: 100%;
+ }
+
+ .col2 {
+ width: 100%;
+ justify-content: center;
+ }
+
+}
\ No newline at end of file
diff --git a/src/components/compounds/searchContainer/SearchContainer.css b/src/components/compounds/searchContainer/SearchContainer.css
index 6241e6a..85a01ef 100644
--- a/src/components/compounds/searchContainer/SearchContainer.css
+++ b/src/components/compounds/searchContainer/SearchContainer.css
@@ -45,4 +45,56 @@
margin-top: 1.5%;
flex-direction: column;
+}
+
+@media (max-width: 650px) {
+.searchContainer {
+ height: 20%;
+ font-family: Verdana, Geneva, Tahoma, sans-serif;
+}
+
+.img {
+ display: block;
+ height: 25%;
+ width: 25%;
+}
+
+.row {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+}
+
+.imgCol {
+ flex: 25%;
+}
+
+.imgCol img{
+ display: block;
+ margin:auto;
+ width: 100%;
+ border-radius: 5%;
+}
+
+.pCol {
+ flex: 75%;
+}
+
+.pCol p{
+ margin: auto;
+ width: 95%;
+ margin-left: auto;
+ margin-right:auto;
+ white-space: pre-wrap;
+ font-family: Verdana, Arial;
+ text-align: left;
+}
+
+.addButton {
+ display: flex;
+ margin-top: 1.5%;
+ flex-direction: column;
+
+}
}
\ No newline at end of file
diff --git a/src/components/compounds/searchContainer/SearchContainer.tsx b/src/components/compounds/searchContainer/SearchContainer.tsx
index 99c79b1..fe70415 100644
--- a/src/components/compounds/searchContainer/SearchContainer.tsx
+++ b/src/components/compounds/searchContainer/SearchContainer.tsx
@@ -34,7 +34,9 @@ const SearchContainer: React.FC = ({
picture: picture,
description: description,
id: id,
- uniquePlantId: uniquePlantId
+ uniquePlantId: uniquePlantId,
+ lastWatered: "",
+ notes: []
}
}, {merge: true});
} catch(error){
diff --git a/src/components/elements/notesPopup/NotesContainer.tsx b/src/components/elements/notesPopup/NotesContainer.tsx
new file mode 100644
index 0000000..271ff63
--- /dev/null
+++ b/src/components/elements/notesPopup/NotesContainer.tsx
@@ -0,0 +1,144 @@
+import React, { useState, useEffect } from 'react';
+import { doc, getDoc, updateDoc } from 'firebase/firestore';
+import { auth, db } from '../../../config/firebase.ts';
+
+interface NotesComponentProps {
+ uniquePlantId: string;
+}
+
+// ...
+
+// ...
+
+const NotesComponent: React.FC = ({ uniquePlantId }) => {
+ const [notes, setNotes] = useState([]);
+ const [newNote, setNewNote] = useState('');
+ const userId = auth.currentUser?.uid;
+
+ // Fetch existing notes when the component mounts
+ useEffect(() => {
+ const fetchNotes = async () => {
+ try {
+ if (!userId) {
+ console.error('User ID is not available.');
+ return;
+ }
+
+ const plantDocRef = doc(db, 'myGarden', userId);
+ const plantDocSnapshot = await getDoc(plantDocRef);
+
+ if (plantDocSnapshot.exists()) {
+ const plantData = plantDocSnapshot.data();
+ const plantMap = plantData?.[uniquePlantId];
+ setNotes(plantMap?.notes || []);
+ }
+ } catch (error) {
+ console.error('Error fetching notes:', error);
+ }
+ };
+
+ fetchNotes();
+ }, [userId, uniquePlantId]);
+
+ // Add a new note
+ const addNote = async () => {
+ try {
+ if (!userId) {
+ console.error('User ID is not available.');
+ return;
+ }
+
+ const plantDocRef = doc(db, 'myGarden', userId);
+ const plantDocSnapshot = await getDoc(plantDocRef);
+
+ if (plantDocSnapshot.exists()) {
+ const currentData = plantDocSnapshot.data();
+ const currentPlantMap = currentData?.[uniquePlantId];
+
+ // Get the current date
+ const currentDate = new Date().toLocaleDateString();
+
+ // Create the new note with the current date
+ const newNoteWithDate = `${currentDate}: ${newNote}`;
+
+ // Merge existing fields with the new 'notes' including the new note
+ const updatedPlantMap = {
+ ...currentPlantMap,
+ notes: [...notes, newNoteWithDate],
+ };
+
+ // Update the document with the merged data
+ await updateDoc(plantDocRef, {
+ [uniquePlantId]: updatedPlantMap,
+ });
+
+ setNotes(updatedPlantMap?.notes || []);
+ setNewNote('');
+ }
+ } catch (error) {
+ console.error('Error adding note:', error);
+ }
+ };
+
+ // Remove a note by index
+ const removeNote = async (index: number) => {
+ try {
+ if (!userId) {
+ console.error('User ID is not available.');
+ return;
+ }
+
+ const plantDocRef = doc(db, 'myGarden', userId);
+ const plantDocSnapshot = await getDoc(plantDocRef);
+
+ if (plantDocSnapshot.exists()) {
+ const currentData = plantDocSnapshot.data();
+ const currentPlantMap = currentData?.[uniquePlantId];
+
+ // Merge existing fields with the updated 'notes'
+ const updatedPlantMap = {
+ ...currentPlantMap,
+ notes: [...notes.slice(0, index), ...notes.slice(index + 1)],
+ };
+
+ // Update the document with the merged data
+ await updateDoc(plantDocRef, {
+ [uniquePlantId]: updatedPlantMap,
+ });
+
+ setNotes(updatedPlantMap?.notes || []);
+ }
+ } catch (error) {
+ console.error('Error removing note:', error);
+ }
+ };
+
+ return (
+
+
Notes
+
+ {notes.map((note, index) => (
+ -
+ {note}
+
+
+ ))}
+
+
+ setNewNote(e.target.value)}
+ placeholder="Add a new note"
+ />
+
+
+
+ );
+ };
+
+ export default NotesComponent;
+
+
+
+
\ No newline at end of file
diff --git a/src/components/elements/notesPopup/NotesPopup.tsx b/src/components/elements/notesPopup/NotesPopup.tsx
new file mode 100644
index 0000000..c81e06b
--- /dev/null
+++ b/src/components/elements/notesPopup/NotesPopup.tsx
@@ -0,0 +1,45 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Button from '../button/Button';
+import Modal from '@mui/material/Modal';
+
+const style = {
+ position: 'absolute' as const,
+ top: '50%',
+ left: '50%',
+ transform: 'translate(-50%, -50%)',
+ width: 400,
+ bgcolor: 'background.paper',
+ border: '2px solid #000',
+ boxShadow: 24,
+ p: 4,
+};
+
+interface PopupProps {
+ children?: React.ReactNode;
+ popupText?: string;
+}
+
+const Popup: React.FC = ({children}) => {
+ const [open, setOpen] = React.useState(false);
+ const handleOpen = () => setOpen(true);
+ const handleClose = () => setOpen(false);
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ );
+};
+
+export default Popup;
\ No newline at end of file
diff --git a/src/components/elements/popup/Popup.css b/src/components/elements/popup/Popup.css
new file mode 100644
index 0000000..c095f5b
--- /dev/null
+++ b/src/components/elements/popup/Popup.css
@@ -0,0 +1,3 @@
+.popup {
+ width: 100%;
+}
\ No newline at end of file
diff --git a/src/components/elements/popup/Popup.tsx b/src/components/elements/popup/Popup.tsx
new file mode 100644
index 0000000..69ac6b6
--- /dev/null
+++ b/src/components/elements/popup/Popup.tsx
@@ -0,0 +1,45 @@
+import * as React from 'react';
+import Box from '@mui/material/Box';
+import Button from '@mui/material/Button';
+import Modal from '@mui/material/Modal';
+
+const style = {
+ position: 'absolute' as const,
+ top: '50%',
+ left: '50%',
+ transform: 'translate(-50%, -50%)',
+ width: 400,
+ bgcolor: 'background.paper',
+ border: '2px solid #000',
+ boxShadow: 24,
+ p: 4,
+};
+
+interface PopupProps {
+ children?: React.ReactNode;
+ popupText?: string;
+}
+
+const Popup: React.FC = ({children, popupText}) => {
+ const [open, setOpen] = React.useState(false);
+ const handleOpen = () => setOpen(true);
+ const handleClose = () => setOpen(false);
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ );
+};
+
+export default Popup;
\ No newline at end of file
diff --git a/src/config/firebase.ts b/src/config/firebase.ts
index 2631410..5a64583 100644
--- a/src/config/firebase.ts
+++ b/src/config/firebase.ts
@@ -3,7 +3,7 @@
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
-
+import { getStorage } from "firebase/storage";
// TODO: Add SDKs for Firebase products that you want to use
@@ -17,7 +17,7 @@ import { getFirestore } from "firebase/firestore";
const firebaseConfig = {
- apiKey: "",
+ apiKey: "AIzaSyDfUO83Q-7_pHvEretn2ZcuH0krfSI6XrA",
authDomain: "plant-papa.firebaseapp.com",
@@ -45,3 +45,6 @@ export const auth = getAuth(app);
//database
export const db = getFirestore(app);
+
+//storage
+export const storage = getStorage(app);
\ No newline at end of file
diff --git a/src/pages/identifyPlants/IdentifyPlants.tsx b/src/pages/identifyPlants/IdentifyPlants.tsx
index aa8b823..e41cbc3 100644
--- a/src/pages/identifyPlants/IdentifyPlants.tsx
+++ b/src/pages/identifyPlants/IdentifyPlants.tsx
@@ -1,11 +1,12 @@
import React from 'react';
+import PlantRecognition from '../../components/compounds/PlantRecognition/PlantRecognition.tsx';
const IdentifyPlants: React.FC = () => {
return (
-
+ <>
+
+ >
);
};
diff --git a/src/pages/myGarden/MyGarden.css b/src/pages/myGarden/MyGarden.css
index d0ac1f4..799136c 100644
--- a/src/pages/myGarden/MyGarden.css
+++ b/src/pages/myGarden/MyGarden.css
@@ -15,4 +15,16 @@
img {
border: 10px;
+}
+
+button {
+
+ margin: 1%;
+}
+
+@media screen and (max-width: 650px) {
+ .grid-container {
+ grid-template-columns: 1fr;
+ }
+
}
\ No newline at end of file
diff --git a/src/pages/myGarden/MyGarden.tsx b/src/pages/myGarden/MyGarden.tsx
index ce728aa..ae3bc09 100644
--- a/src/pages/myGarden/MyGarden.tsx
+++ b/src/pages/myGarden/MyGarden.tsx
@@ -2,9 +2,13 @@ import React, {useEffect, useState} from 'react';
import './MyGarden.css';
import Container from '../../components/elements/container/Container';
import { auth, db } from '../../config/firebase.ts';
-import { getDocs, collection, DocumentData, DocumentSnapshot, doc, updateDoc, deleteField } from "firebase/firestore";
+import { getDocs, collection, DocumentData, DocumentSnapshot, doc, updateDoc, deleteField, getDoc} from "firebase/firestore";
import Button from '../../components/elements/button/Button.tsx';
import PlantInfo from '../../components/elements/plantInformation/PlantInfo.tsx';
+import Popup from '../../components/elements/popup/Popup.tsx';
+import AddPlant from '../../components/compounds/addPlant/AddPlant.tsx';
+import NotesPopup from '../../components/elements/notesPopup/NotesPopup.tsx';
+import NotesContainer from '../../components/elements/notesPopup/NotesContainer.tsx';
@@ -15,6 +19,7 @@ interface Plant{
scientificName: string;
picture: string;
description: string;
+ lastWatered: string;
}
@@ -63,17 +68,64 @@ const MyGarden: React.FC = () => {
};
+
+
+
+
const handleRemovePlant = async (plantId: string) => {
- try{
- const plantRef = doc(db, "myGarden", auth.currentUser?.uid as string);
- await updateDoc(plantRef, {
- [plantId]: deleteField()
- })
+
+ if(confirm("Are you sure you want to remove this plant?")){
+ try{
+ const plantRef = doc(db, "myGarden", auth.currentUser?.uid as string);
+ await updateDoc(plantRef, {
+ [plantId]: deleteField()
+ })
+ getGarden();
+ }catch(error){
+ console.error(error);
+ }
+ }
+ };
+
+
+ const handleWaterPlant = async (plantId: string) => {
+ try {
+ const userId = auth.currentUser?.uid as string;
+ const userDocRef = doc(db, "myGarden", userId);
+
+ // Fetch the current document data
+ const userDocSnapshot = await getDoc(userDocRef);
+
+ if (userDocSnapshot.exists()) {
+ const userData = userDocSnapshot.data();
+
+ if (userData) {
+ const plantData = userData[plantId];
+
+ if (plantData) {
+ const currentTime: string = new Date().toLocaleString();
+
+ // Update the lastWatered field for the specific plant
+ await updateDoc(userDocRef, {
+ [`${plantId}.lastWatered`]: currentTime,
+ });
+
+ console.log("Plant watered successfully!");
+ } else {
+ console.error("No plant data found for plantId:", plantId);
+ }
+ }
+ } else {
+ console.error("User document not found for userId:", userId);
+ }
+
getGarden();
- }catch(error){
- console.error(error);
+ } catch (error) {
+ console.error("Error watering plant: ", error);
}
};
+
+
useEffect(() => {
@@ -85,25 +137,34 @@ const MyGarden: React.FC = () => {
return (
<>
- {userData?.map((user) => (
-
+
+
+
+ {userData?.map((user) => (
+
+
+ {user.plants.map((plant) => (
+
+
+
+ {plant.plantName}
+ {plant.scientificName}
+
+
+
+
- {user.plants.map((plant) => (
-
-
{}
-
- {plant.plantName}
- {plant.scientificName}
-
-
-
- handleRemovePlant(plant.uniquePlantId)} />
-
-
-
- ))}
- ))}
+ ))}
+
+ ))}
>
);
};
diff --git a/src/pages/userAuthentication/UserAuthentication.tsx b/src/pages/userAuthentication/UserAuthentication.tsx
index 8cf2d41..b3700d5 100644
--- a/src/pages/userAuthentication/UserAuthentication.tsx
+++ b/src/pages/userAuthentication/UserAuthentication.tsx
@@ -14,6 +14,7 @@ import { db } from '../../config/firebase.ts';
import { doc, setDoc } from "firebase/firestore";
+
const UserAuthentication: React.FC = () => {
@@ -74,6 +75,7 @@ const UserAuthentication: React.FC = () => {
}
};
+
return (
diff --git a/upload/images/images_1703294296146.jpg b/upload/images/images_1703294296146.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703294296146.jpg differ
diff --git a/upload/images/images_1703294317202.jpg b/upload/images/images_1703294317202.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703294317202.jpg differ
diff --git a/upload/images/images_1703294557708.jpg b/upload/images/images_1703294557708.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703294557708.jpg differ
diff --git a/upload/images/images_1703294658332.jpg b/upload/images/images_1703294658332.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703294658332.jpg differ
diff --git a/upload/images/images_1703294680926.jpg b/upload/images/images_1703294680926.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703294680926.jpg differ
diff --git a/upload/images/images_1703294832345.jpg b/upload/images/images_1703294832345.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703294832345.jpg differ
diff --git a/upload/images/images_1703294929384.jpg b/upload/images/images_1703294929384.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703294929384.jpg differ
diff --git a/upload/images/images_1703294949910.jpg b/upload/images/images_1703294949910.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703294949910.jpg differ
diff --git a/upload/images/images_1703295024106.jpg b/upload/images/images_1703295024106.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703295024106.jpg differ
diff --git a/upload/images/images_1703295182697.jpg b/upload/images/images_1703295182697.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703295182697.jpg differ
diff --git a/upload/images/images_1703295283501.jpg b/upload/images/images_1703295283501.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703295283501.jpg differ
diff --git a/upload/images/images_1703295407463.jpg b/upload/images/images_1703295407463.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703295407463.jpg differ
diff --git a/upload/images/images_1703295528025.jpg b/upload/images/images_1703295528025.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703295528025.jpg differ
diff --git a/upload/images/images_1703295614877.jpg b/upload/images/images_1703295614877.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703295614877.jpg differ
diff --git a/upload/images/images_1703295715476.jpg b/upload/images/images_1703295715476.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703295715476.jpg differ
diff --git a/upload/images/images_1703295894296.jpg b/upload/images/images_1703295894296.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703295894296.jpg differ
diff --git a/upload/images/images_1703296055908.jpg b/upload/images/images_1703296055908.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703296055908.jpg differ
diff --git a/upload/images/images_1703296193619.jpg b/upload/images/images_1703296193619.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703296193619.jpg differ
diff --git a/upload/images/images_1703296315393.jpg b/upload/images/images_1703296315393.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703296315393.jpg differ
diff --git a/upload/images/images_1703296479835.jpg b/upload/images/images_1703296479835.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703296479835.jpg differ
diff --git a/upload/images/images_1703296505499.jpg b/upload/images/images_1703296505499.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703296505499.jpg differ
diff --git a/upload/images/images_1703296759290.jpg b/upload/images/images_1703296759290.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703296759290.jpg differ
diff --git a/upload/images/images_1703296988220.jpg b/upload/images/images_1703296988220.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703296988220.jpg differ
diff --git a/upload/images/images_1703297030493.jpg b/upload/images/images_1703297030493.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703297030493.jpg differ
diff --git a/upload/images/images_1703297093749.jpg b/upload/images/images_1703297093749.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703297093749.jpg differ
diff --git a/upload/images/images_1703297137057.jpg b/upload/images/images_1703297137057.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703297137057.jpg differ
diff --git a/upload/images/images_1703297168804.jpg b/upload/images/images_1703297168804.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703297168804.jpg differ
diff --git a/upload/images/images_1703297224642.jpg b/upload/images/images_1703297224642.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703297224642.jpg differ
diff --git a/upload/images/images_1703297299051.jpg b/upload/images/images_1703297299051.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703297299051.jpg differ
diff --git a/upload/images/images_1703297315005.jpg b/upload/images/images_1703297315005.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703297315005.jpg differ
diff --git a/upload/images/images_1703297391871.jpg b/upload/images/images_1703297391871.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703297391871.jpg differ
diff --git a/upload/images/images_1703297436101.jpg b/upload/images/images_1703297436101.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703297436101.jpg differ
diff --git a/upload/images/images_1703297476854.jpg b/upload/images/images_1703297476854.jpg
new file mode 100644
index 0000000..6abd7f9
Binary files /dev/null and b/upload/images/images_1703297476854.jpg differ
diff --git a/upload/images/images_1703297888372.jpg b/upload/images/images_1703297888372.jpg
new file mode 100644
index 0000000..3e614a8
Binary files /dev/null and b/upload/images/images_1703297888372.jpg differ
diff --git a/upload/images/images_1703297927786.jpg b/upload/images/images_1703297927786.jpg
new file mode 100644
index 0000000..3e614a8
Binary files /dev/null and b/upload/images/images_1703297927786.jpg differ
diff --git a/upload/images/images_1703298008155.jpg b/upload/images/images_1703298008155.jpg
new file mode 100644
index 0000000..3e614a8
Binary files /dev/null and b/upload/images/images_1703298008155.jpg differ
diff --git a/upload/images/images_1703298148892.jpg b/upload/images/images_1703298148892.jpg
new file mode 100644
index 0000000..3e614a8
Binary files /dev/null and b/upload/images/images_1703298148892.jpg differ
diff --git a/upload/images/images_1703298169798.jpg b/upload/images/images_1703298169798.jpg
new file mode 100644
index 0000000..3e614a8
Binary files /dev/null and b/upload/images/images_1703298169798.jpg differ
diff --git a/upload/images/images_1703298489396.jpg b/upload/images/images_1703298489396.jpg
new file mode 100644
index 0000000..3e614a8
Binary files /dev/null and b/upload/images/images_1703298489396.jpg differ
diff --git a/upload/images/images_1703298549244.jpg b/upload/images/images_1703298549244.jpg
new file mode 100644
index 0000000..3e614a8
Binary files /dev/null and b/upload/images/images_1703298549244.jpg differ
diff --git a/upload/images/images_1703298563451.jpg b/upload/images/images_1703298563451.jpg
new file mode 100644
index 0000000..3e614a8
Binary files /dev/null and b/upload/images/images_1703298563451.jpg differ