From 61330728ca0f21145efa4a554bd43c1a941c1f7d Mon Sep 17 00:00:00 2001 From: Wdrew232 Date: Wed, 23 Apr 2025 23:55:17 +0000 Subject: [PATCH 01/39] chamged name of website --- src/front/pages/Home.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/front/pages/Home.jsx b/src/front/pages/Home.jsx index 341ed21768..a9884c0ed9 100644 --- a/src/front/pages/Home.jsx +++ b/src/front/pages/Home.jsx @@ -34,7 +34,7 @@ export const Home = () => { return (
-

Hello Rigo!!

+

Cocktail!!

Rigo Baby

From ac7f746526d703eb6aaef3eabd336d04bb2fdd23 Mon Sep 17 00:00:00 2001 From: XxDrancerxX Date: Wed, 23 Apr 2025 23:55:23 +0000 Subject: [PATCH 02/39] done --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 27a99f796e..1f99a8ec33 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,7 @@ - Hello Rigo + Lyon and Wolf
From 65bcdff99a27aa94a41491ce0e49d03cb42a4448 Mon Sep 17 00:00:00 2001 From: Wdrew232 Date: Wed, 23 Apr 2025 23:59:34 +0000 Subject: [PATCH 03/39] changed web name --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 27a99f796e..6b080ad58d 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,7 @@ - Hello Rigo + Cocktail
From aad9bc0795503000050e5f6e382cb7bf83018498 Mon Sep 17 00:00:00 2001 From: Jackbringas <102544709+Jackbringas@users.noreply.github.com> Date: Thu, 24 Apr 2025 00:04:09 +0000 Subject: [PATCH 04/39] Readme test --- README.md | 81 ------------------------------------------------------ READMEE.md | 4 +++ 2 files changed, 4 insertions(+), 81 deletions(-) create mode 100644 READMEE.md diff --git a/README.md b/README.md index 6b782b220d..e69de29bb2 100644 --- a/README.md +++ b/README.md @@ -1,81 +0,0 @@ -# WebApp boilerplate with React JS and Flask API - -Build web applications using React.js for the front end and python/flask for your backend API. - -- Documentation can be found here: https://4geeks.com/docs/start/react-flask-template -- Here is a video on [how to use this template](https://www.loom.com/share/f37c6838b3f1496c95111e515e83dd9b) -- Integrated with Pipenv for package managing. -- Fast deployment to Render [in just a few steps here](https://4geeks.com/docs/start/deploy-to-render-com). -- Use of .env file. -- SQLAlchemy integration for database abstraction. - -### 1) Installation: - -> If you use Github Codespaces (recommended) or Gitpod this template will already come with Python, Node and the Posgres Database installed. If you are working locally make sure to install Python 3.10, Node - -It is recomended to install the backend first, make sure you have Python 3.10, Pipenv and a database engine (Posgress recomended) - -1. Install the python packages: `$ pipenv install` -2. Create a .env file based on the .env.example: `$ cp .env.example .env` -3. Install your database engine and create your database, depending on your database you have to create a DATABASE_URL variable with one of the possible values, make sure you replace the valudes with your database information: - -| Engine | DATABASE_URL | -| --------- | --------------------------------------------------- | -| SQLite | sqlite:////test.db | -| MySQL | mysql://username:password@localhost:port/example | -| Postgress | postgres://username:password@localhost:5432/example | - -4. Migrate the migrations: `$ pipenv run migrate` (skip if you have not made changes to the models on the `./src/api/models.py`) -5. Run the migrations: `$ pipenv run upgrade` -6. Run the application: `$ pipenv run start` - -> Note: Codespaces users can connect to psql by typing: `psql -h localhost -U gitpod example` - -### Undo a migration - -You are also able to undo a migration by running - -```sh -$ pipenv run downgrade -``` - -### Backend Populate Table Users - -To insert test users in the database execute the following command: - -```sh -$ flask insert-test-users 5 -``` - -And you will see the following message: - -``` - Creating test users - test_user1@test.com created. - test_user2@test.com created. - test_user3@test.com created. - test_user4@test.com created. - test_user5@test.com created. - Users created successfully! -``` - -### **Important note for the database and the data inside it** - -Every Github codespace environment will have **its own database**, so if you're working with more people eveyone will have a different database and different records inside it. This data **will be lost**, so don't spend too much time manually creating records for testing, instead, you can automate adding records to your database by editing ```commands.py``` file inside ```/src/api``` folder. Edit line 32 function ```insert_test_data``` to insert the data according to your model (use the function ```insert_test_users``` above as an example). Then, all you need to do is run ```pipenv run insert-test-data```. - -### Front-End Manual Installation: - -- Make sure you are using node version 20 and that you have already successfully installed and runned the backend. - -1. Install the packages: `$ npm install` -2. Start coding! start the webpack dev server `$ npm run start` - -## Publish your website! - -This boilerplate it's 100% read to deploy with Render.com and Heroku in a matter of minutes. Please read the [official documentation about it](https://4geeks.com/docs/start/deploy-to-render-com). - -### Contributors - -This template was built as part of the 4Geeks Academy [Coding Bootcamp](https://4geeksacademy.com/us/coding-bootcamp) by [Alejandro Sanchez](https://twitter.com/alesanchezr) and many other contributors. Find out more about our [Full Stack Developer Course](https://4geeksacademy.com/us/coding-bootcamps/part-time-full-stack-developer), and [Data Science Bootcamp](https://4geeksacademy.com/us/coding-bootcamps/datascience-machine-learning). - -You can find other templates and resources like this at the [school github page](https://github.com/4geeksacademy/). diff --git a/READMEE.md b/READMEE.md new file mode 100644 index 0000000000..f6c02befdf --- /dev/null +++ b/READMEE.md @@ -0,0 +1,4 @@ +team members: +Drew Wilson +Israel Diaz +Jacqueline Bringas \ No newline at end of file From b1646a2966549fa627d7afffcdcf4d98827cbd57 Mon Sep 17 00:00:00 2001 From: XxDrancerxX Date: Sat, 26 Apr 2025 01:03:07 +0000 Subject: [PATCH 05/39] building first endpoint --- package-lock.json | 1286 +++++++++++++++++++++++++++++++++------------ package.json | 28 +- src/api/routes.py | 20 +- src/app.py | 2 +- 4 files changed, 985 insertions(+), 351 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8d43d98ab7..c6d830fbeb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.18.0" + "react-router-dom": "^6.30.0" }, "devDependencies": { "@types/react": "^18.2.18", @@ -22,7 +22,7 @@ "eslint-plugin-react": "^7.33.1", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.3", - "vite": "^4.4.8" + "vite": "^6.3.3" }, "engines": { "node": ">=20.0.0" @@ -229,27 +229,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz", - "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.7" + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", - "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.26.7" + "@babel/types": "^7.27.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -291,15 +291,15 @@ } }, "node_modules/@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" }, "engines": { "node": ">=6.9.0" @@ -325,9 +325,9 @@ } }, "node_modules/@babel/types": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", - "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", "dev": true, "license": "MIT", "dependencies": { @@ -338,10 +338,27 @@ "node": ">=6.9.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", + "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz", + "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", "cpu": [ "arm" ], @@ -352,13 +369,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz", + "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", "cpu": [ "arm64" ], @@ -369,13 +386,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz", + "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", "cpu": [ "x64" ], @@ -386,13 +403,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz", + "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", "cpu": [ "arm64" ], @@ -403,13 +420,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz", + "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", "cpu": [ "x64" ], @@ -420,13 +437,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz", + "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", "cpu": [ "arm64" ], @@ -437,13 +454,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz", + "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", "cpu": [ "x64" ], @@ -454,13 +471,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz", + "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", "cpu": [ "arm" ], @@ -471,13 +488,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz", + "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", "cpu": [ "arm64" ], @@ -488,13 +505,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz", + "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", "cpu": [ "ia32" ], @@ -505,13 +522,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz", + "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", "cpu": [ "loong64" ], @@ -522,13 +539,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz", + "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", "cpu": [ "mips64el" ], @@ -539,13 +556,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz", + "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", "cpu": [ "ppc64" ], @@ -556,13 +573,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz", + "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", "cpu": [ "riscv64" ], @@ -573,13 +590,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz", + "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", "cpu": [ "s390x" ], @@ -590,13 +607,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz", + "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", "cpu": [ "x64" ], @@ -607,13 +624,30 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz", + "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz", + "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", "cpu": [ "x64" ], @@ -624,13 +658,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz", + "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz", + "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", "cpu": [ "x64" ], @@ -641,13 +692,13 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz", + "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", "cpu": [ "x64" ], @@ -658,13 +709,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz", + "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", "cpu": [ "arm64" ], @@ -675,13 +726,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz", + "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", "cpu": [ "ia32" ], @@ -692,13 +743,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz", + "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", "cpu": [ "x64" ], @@ -709,7 +760,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -944,14 +995,294 @@ } }, "node_modules/@remix-run/router": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.22.0.tgz", - "integrity": "sha512-MBOl8MeOzpK0HQQQshKB7pABXbmyHizdTpqnrIseTbsv0nAepwC2ENZa1aaBExNQcpLoXmWthhak8SABLzvGPw==", + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", "license": "MIT", "engines": { "node": ">=14.0.0" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -997,13 +1328,12 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/node": { - "version": "16.11.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", - "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==", + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "dev": true, - "optional": true, - "peer": true + "license": "MIT" }, "node_modules/@types/prop-types": { "version": "15.7.14", @@ -1743,9 +2073,9 @@ } }, "node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", + "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1753,31 +2083,34 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" + "@esbuild/aix-ppc64": "0.25.3", + "@esbuild/android-arm": "0.25.3", + "@esbuild/android-arm64": "0.25.3", + "@esbuild/android-x64": "0.25.3", + "@esbuild/darwin-arm64": "0.25.3", + "@esbuild/darwin-x64": "0.25.3", + "@esbuild/freebsd-arm64": "0.25.3", + "@esbuild/freebsd-x64": "0.25.3", + "@esbuild/linux-arm": "0.25.3", + "@esbuild/linux-arm64": "0.25.3", + "@esbuild/linux-ia32": "0.25.3", + "@esbuild/linux-loong64": "0.25.3", + "@esbuild/linux-mips64el": "0.25.3", + "@esbuild/linux-ppc64": "0.25.3", + "@esbuild/linux-riscv64": "0.25.3", + "@esbuild/linux-s390x": "0.25.3", + "@esbuild/linux-x64": "0.25.3", + "@esbuild/netbsd-arm64": "0.25.3", + "@esbuild/netbsd-x64": "0.25.3", + "@esbuild/openbsd-arm64": "0.25.3", + "@esbuild/openbsd-x64": "0.25.3", + "@esbuild/sunos-x64": "0.25.3", + "@esbuild/win32-arm64": "0.25.3", + "@esbuild/win32-ia32": "0.25.3", + "@esbuild/win32-x64": "0.25.3" } }, "node_modules/escalade": { @@ -2240,6 +2573,21 @@ "reusify": "^1.0.4" } }, + "node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -2294,11 +2642,12 @@ "dev": true }, "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -3160,9 +3509,9 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true, "funding": [ { @@ -3392,6 +3741,19 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -3403,9 +3765,9 @@ } }, "node_modules/postcss": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", - "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "dev": true, "funding": [ { @@ -3522,12 +3884,12 @@ } }, "node_modules/react-router": { - "version": "6.29.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.29.0.tgz", - "integrity": "sha512-DXZJoE0q+KyeVw75Ck6GkPxFak63C4fGqZGNijnWgzB/HzSP1ZfTlBj5COaGWwhrMQ/R8bXiq5Ooy4KG+ReyjQ==", + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz", + "integrity": "sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.22.0" + "@remix-run/router": "1.23.0" }, "engines": { "node": ">=14.0.0" @@ -3537,13 +3899,13 @@ } }, "node_modules/react-router-dom": { - "version": "6.29.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.29.0.tgz", - "integrity": "sha512-pkEbJPATRJ2iotK+wUwHfy0xs2T59YPEN8BQxVCPeBZvK7kfPESRc/nyxzdcxR17hXgUPYx2whMwl+eo9cUdnQ==", + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.0.tgz", + "integrity": "sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==", "license": "MIT", "dependencies": { - "@remix-run/router": "1.22.0", - "react-router": "6.29.0" + "@remix-run/router": "1.23.0", + "react-router": "6.30.0" }, "engines": { "node": ">=14.0.0" @@ -3632,19 +3994,42 @@ } }, "node_modules/rollup": { - "version": "3.29.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", - "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", "dev": true, "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, "bin": { "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=14.18.0", + "node": ">=18.0.0", "npm": ">=8.0.0" }, "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", "fsevents": "~2.3.2" } }, @@ -4109,6 +4494,23 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4273,41 +4675,51 @@ } }, "node_modules/vite": { - "version": "4.5.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.9.tgz", - "integrity": "sha512-qK9W4xjgD3gXbC0NmdNFFnVFLMWSNiR3swj957yutwzzN16xF/E7nmtAyp1rT9hviDroQANjE4HK3H4WqWdFtw==", + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.3.tgz", + "integrity": "sha512-5nXH+QsELbFKhsEfWLkHrvgRpTdGJzqOZ+utSdmPTvwHmvU6ITTm3xx+mRusihkcI8GeC7lCDyn3kDtiki9scw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.27", - "rollup": "^3.27.1" + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" }, "optionalDependencies": { - "fsevents": "~2.3.2" + "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": ">= 14", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -4317,6 +4729,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, @@ -4325,6 +4740,12 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, @@ -4615,22 +5036,22 @@ "dev": true }, "@babel/helpers": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.7.tgz", - "integrity": "sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", + "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", "dev": true, "requires": { - "@babel/template": "^7.25.9", - "@babel/types": "^7.26.7" + "@babel/template": "^7.27.0", + "@babel/types": "^7.27.0" } }, "@babel/parser": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.7.tgz", - "integrity": "sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", "dev": true, "requires": { - "@babel/types": "^7.26.7" + "@babel/types": "^7.27.0" } }, "@babel/plugin-transform-react-jsx-self": { @@ -4652,14 +5073,14 @@ } }, "@babel/template": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", - "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", + "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", "dev": true, "requires": { - "@babel/code-frame": "^7.25.9", - "@babel/parser": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/code-frame": "^7.26.2", + "@babel/parser": "^7.27.0", + "@babel/types": "^7.27.0" } }, "@babel/traverse": { @@ -4678,166 +5099,187 @@ } }, "@babel/types": { - "version": "7.26.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.7.tgz", - "integrity": "sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", "dev": true, "requires": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, + "@esbuild/aix-ppc64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", + "integrity": "sha512-W8bFfPA8DowP8l//sxjJLSLkD8iEjMc7cBVyP+u4cEv9sM7mdUCkgsj+t0n/BWPFtv7WWCN5Yzj0N6FJNUUqBQ==", + "dev": true, + "optional": true + }, "@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.3.tgz", + "integrity": "sha512-PuwVXbnP87Tcff5I9ngV0lmiSu40xw1At6i3GsU77U7cjDDB4s0X2cyFuBiDa1SBk9DnvWwnGvVaGBqoFWPb7A==", "dev": true, "optional": true }, "@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.3.tgz", + "integrity": "sha512-XelR6MzjlZuBM4f5z2IQHK6LkK34Cvv6Rj2EntER3lwCBFdg6h2lKbtRjpTTsdEjD/WSe1q8UyPBXP1x3i/wYQ==", "dev": true, "optional": true }, "@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.3.tgz", + "integrity": "sha512-ogtTpYHT/g1GWS/zKM0cc/tIebFjm1F9Aw1boQ2Y0eUQ+J89d0jFY//s9ei9jVIlkYi8AfOjiixcLJSGNSOAdQ==", "dev": true, "optional": true }, "@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.3.tgz", + "integrity": "sha512-eESK5yfPNTqpAmDfFWNsOhmIOaQA59tAcF/EfYvo5/QWQCzXn5iUSOnqt3ra3UdzBv073ykTtmeLJZGt3HhA+w==", "dev": true, "optional": true }, "@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.3.tgz", + "integrity": "sha512-Kd8glo7sIZtwOLcPbW0yLpKmBNWMANZhrC1r6K++uDR2zyzb6AeOYtI6udbtabmQpFaxJ8uduXMAo1gs5ozz8A==", "dev": true, "optional": true }, "@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.3.tgz", + "integrity": "sha512-EJiyS70BYybOBpJth3M0KLOus0n+RRMKTYzhYhFeMwp7e/RaajXvP+BWlmEXNk6uk+KAu46j/kaQzr6au+JcIw==", "dev": true, "optional": true }, "@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.3.tgz", + "integrity": "sha512-Q+wSjaLpGxYf7zC0kL0nDlhsfuFkoN+EXrx2KSB33RhinWzejOd6AvgmP5JbkgXKmjhmpfgKZq24pneodYqE8Q==", "dev": true, "optional": true }, "@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.3.tgz", + "integrity": "sha512-dUOVmAUzuHy2ZOKIHIKHCm58HKzFqd+puLaS424h6I85GlSDRZIA5ycBixb3mFgM0Jdh+ZOSB6KptX30DD8YOQ==", "dev": true, "optional": true }, "@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.3.tgz", + "integrity": "sha512-xCUgnNYhRD5bb1C1nqrDV1PfkwgbswTTBRbAd8aH5PhYzikdf/ddtsYyMXFfGSsb/6t6QaPSzxtbfAZr9uox4A==", "dev": true, "optional": true }, "@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.3.tgz", + "integrity": "sha512-yplPOpczHOO4jTYKmuYuANI3WhvIPSVANGcNUeMlxH4twz/TeXuzEP41tGKNGWJjuMhotpGabeFYGAOU2ummBw==", "dev": true, "optional": true }, "@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.3.tgz", + "integrity": "sha512-P4BLP5/fjyihmXCELRGrLd793q/lBtKMQl8ARGpDxgzgIKJDRJ/u4r1A/HgpBpKpKZelGct2PGI4T+axcedf6g==", "dev": true, "optional": true }, "@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.3.tgz", + "integrity": "sha512-eRAOV2ODpu6P5divMEMa26RRqb2yUoYsuQQOuFUexUoQndm4MdpXXDBbUoKIc0iPa4aCO7gIhtnYomkn2x+bag==", "dev": true, "optional": true }, "@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.3.tgz", + "integrity": "sha512-ZC4jV2p7VbzTlnl8nZKLcBkfzIf4Yad1SJM4ZMKYnJqZFD4rTI+pBG65u8ev4jk3/MPwY9DvGn50wi3uhdaghg==", "dev": true, "optional": true }, "@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.3.tgz", + "integrity": "sha512-LDDODcFzNtECTrUUbVCs6j9/bDVqy7DDRsuIXJg6so+mFksgwG7ZVnTruYi5V+z3eE5y+BJZw7VvUadkbfg7QA==", "dev": true, "optional": true }, "@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.3.tgz", + "integrity": "sha512-s+w/NOY2k0yC2p9SLen+ymflgcpRkvwwa02fqmAwhBRI3SC12uiS10edHHXlVWwfAagYSY5UpmT/zISXPMW3tQ==", "dev": true, "optional": true }, "@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.3.tgz", + "integrity": "sha512-nQHDz4pXjSDC6UfOE1Fw9Q8d6GCAd9KdvMZpfVGWSJztYCarRgSDfOVBY5xwhQXseiyxapkiSJi/5/ja8mRFFA==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.3.tgz", + "integrity": "sha512-1QaLtOWq0mzK6tzzp0jRN3eccmN3hezey7mhLnzC6oNlJoUJz4nym5ZD7mDnS/LZQgkrhEbEiTn515lPeLpgWA==", "dev": true, "optional": true }, "@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.3.tgz", + "integrity": "sha512-i5Hm68HXHdgv8wkrt+10Bc50zM0/eonPb/a/OFVfB6Qvpiirco5gBA5bz7S2SHuU+Y4LWn/zehzNX14Sp4r27g==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-arm64": { + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.3.tgz", + "integrity": "sha512-zGAVApJEYTbOC6H/3QBr2mq3upG/LBEXr85/pTtKiv2IXcgKV0RT0QA/hSXZqSvLEpXeIxah7LczB4lkiYhTAQ==", "dev": true, "optional": true }, "@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.3.tgz", + "integrity": "sha512-fpqctI45NnCIDKBH5AXQBsD0NDPbEFczK98hk/aa6HJxbl+UtLkJV2+Bvy5hLSLk3LHmqt0NTkKNso1A9y1a4w==", "dev": true, "optional": true }, "@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.3.tgz", + "integrity": "sha512-ROJhm7d8bk9dMCUZjkS8fgzsPAZEjtRJqCAmVgB0gMrvG7hfmPmz9k1rwO4jSiblFjYmNvbECL9uhaPzONMfgA==", "dev": true, "optional": true }, "@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.3.tgz", + "integrity": "sha512-YWcow8peiHpNBiIXHwaswPnAXLsLVygFwCB3A7Bh5jRkIBFWHGmNQ48AlX4xDvQNoMZlPYzjVOQDYEzWCqufMQ==", "dev": true, "optional": true }, "@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.3.tgz", + "integrity": "sha512-qspTZOIGoXVS4DpNqUYUs9UxVb04khS1Degaw/MnfMe7goQ3lTfQ13Vw4qY/Nj0979BGvMRpAYbs/BAxEvU8ew==", "dev": true, "optional": true }, "@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.3.tgz", + "integrity": "sha512-ICgUR+kPimx0vvRzf+N/7L7tVSQeE3BYY+NhHRHXS1kBuPO7z2+7ea2HbhDyZdTephgvNvKrlDDKUexuCVBVvg==", "dev": true, "optional": true }, @@ -4999,9 +5441,149 @@ } }, "@remix-run/router": { - "version": "1.22.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.22.0.tgz", - "integrity": "sha512-MBOl8MeOzpK0HQQQshKB7pABXbmyHizdTpqnrIseTbsv0nAepwC2ENZa1aaBExNQcpLoXmWthhak8SABLzvGPw==" + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", + "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==" + }, + "@rollup/rollup-android-arm-eabi": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-android-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-darwin-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-freebsd-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-freebsd-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-arm64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-linux-x64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "dev": true, + "optional": true + }, + "@rollup/rollup-win32-x64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "dev": true, + "optional": true }, "@types/babel__core": { "version": "7.20.5", @@ -5044,13 +5626,11 @@ "@babel/types": "^7.20.7" } }, - "@types/node": { - "version": "16.11.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", - "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==", - "dev": true, - "optional": true, - "peer": true + "@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true }, "@types/prop-types": { "version": "15.7.14", @@ -5560,33 +6140,36 @@ } }, "esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", - "dev": true, - "requires": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" + "version": "0.25.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", + "integrity": "sha512-qKA6Pvai73+M2FtftpNKRxJ78GIjmFXFxd/1DVBqGo/qNhLSfv+G12n9pNoWdytJC8U00TrViOwpjT0zgqQS8Q==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.25.3", + "@esbuild/android-arm": "0.25.3", + "@esbuild/android-arm64": "0.25.3", + "@esbuild/android-x64": "0.25.3", + "@esbuild/darwin-arm64": "0.25.3", + "@esbuild/darwin-x64": "0.25.3", + "@esbuild/freebsd-arm64": "0.25.3", + "@esbuild/freebsd-x64": "0.25.3", + "@esbuild/linux-arm": "0.25.3", + "@esbuild/linux-arm64": "0.25.3", + "@esbuild/linux-ia32": "0.25.3", + "@esbuild/linux-loong64": "0.25.3", + "@esbuild/linux-mips64el": "0.25.3", + "@esbuild/linux-ppc64": "0.25.3", + "@esbuild/linux-riscv64": "0.25.3", + "@esbuild/linux-s390x": "0.25.3", + "@esbuild/linux-x64": "0.25.3", + "@esbuild/netbsd-arm64": "0.25.3", + "@esbuild/netbsd-x64": "0.25.3", + "@esbuild/openbsd-arm64": "0.25.3", + "@esbuild/openbsd-x64": "0.25.3", + "@esbuild/sunos-x64": "0.25.3", + "@esbuild/win32-arm64": "0.25.3", + "@esbuild/win32-ia32": "0.25.3", + "@esbuild/win32-x64": "0.25.3" } }, "escalade": { @@ -5907,6 +6490,13 @@ "reusify": "^1.0.4" } }, + "fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "requires": {} + }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -5948,9 +6538,9 @@ "dev": true }, "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "optional": true }, @@ -6497,9 +7087,9 @@ "dev": true }, "nanoid": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", - "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "dev": true }, "natural-compare": { @@ -6653,6 +7243,12 @@ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true }, + "picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true + }, "possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -6660,9 +7256,9 @@ "dev": true }, "postcss": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", - "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", "dev": true, "requires": { "nanoid": "^3.3.8", @@ -6727,20 +7323,20 @@ "dev": true }, "react-router": { - "version": "6.29.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.29.0.tgz", - "integrity": "sha512-DXZJoE0q+KyeVw75Ck6GkPxFak63C4fGqZGNijnWgzB/HzSP1ZfTlBj5COaGWwhrMQ/R8bXiq5Ooy4KG+ReyjQ==", + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz", + "integrity": "sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==", "requires": { - "@remix-run/router": "1.22.0" + "@remix-run/router": "1.23.0" } }, "react-router-dom": { - "version": "6.29.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.29.0.tgz", - "integrity": "sha512-pkEbJPATRJ2iotK+wUwHfy0xs2T59YPEN8BQxVCPeBZvK7kfPESRc/nyxzdcxR17hXgUPYx2whMwl+eo9cUdnQ==", + "version": "6.30.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.0.tgz", + "integrity": "sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==", "requires": { - "@remix-run/router": "1.22.0", - "react-router": "6.29.0" + "@remix-run/router": "1.23.0", + "react-router": "6.30.0" } }, "reflect.getprototypeof": { @@ -6795,11 +7391,32 @@ } }, "rollup": { - "version": "3.29.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", - "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", - "dev": true, - "requires": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "dev": true, + "requires": { + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", + "@types/estree": "1.0.7", "fsevents": "~2.3.2" } }, @@ -7124,6 +7741,16 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "requires": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + } + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -7224,15 +7851,18 @@ } }, "vite": { - "version": "4.5.9", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.9.tgz", - "integrity": "sha512-qK9W4xjgD3gXbC0NmdNFFnVFLMWSNiR3swj957yutwzzN16xF/E7nmtAyp1rT9hviDroQANjE4HK3H4WqWdFtw==", - "dev": true, - "requires": { - "esbuild": "^0.18.10", - "fsevents": "~2.3.2", - "postcss": "^8.4.27", - "rollup": "^3.27.1" + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.3.tgz", + "integrity": "sha512-5nXH+QsELbFKhsEfWLkHrvgRpTdGJzqOZ+utSdmPTvwHmvU6ITTm3xx+mRusihkcI8GeC7lCDyn3kDtiki9scw==", + "dev": true, + "requires": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "fsevents": "~2.3.3", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" } }, "which": { diff --git a/package.json b/package.json index 0caab10749..0eb5d90213 100755 --- a/package.json +++ b/package.json @@ -8,10 +8,10 @@ "main": "index.js", "scripts": { "dev": "vite", - "start": "vite", - "build": "vite build", - "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview" + "start": "vite", + "build": "vite build", + "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" }, "author": { "name": "Alejandro Sanchez", @@ -30,13 +30,13 @@ "license": "ISC", "devDependencies": { "@types/react": "^18.2.18", - "@types/react-dom": "^18.2.7", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.46.0", - "eslint-plugin-react": "^7.33.1", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.3", - "vite": "^4.4.8" + "@types/react-dom": "^18.2.7", + "@vitejs/plugin-react": "^4.0.4", + "eslint": "^8.46.0", + "eslint-plugin-react": "^7.33.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.3", + "vite": "^6.3.3" }, "babel": { "presets": [ @@ -55,8 +55,8 @@ }, "dependencies": { "prop-types": "^15.8.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-router-dom": "^6.18.0" + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.30.0" } } diff --git a/src/api/routes.py b/src/api/routes.py index 029589a3a1..00017709c9 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -6,17 +6,21 @@ from api.utils import generate_sitemap, APIException from flask_cors import CORS -api = Blueprint('api', __name__) +api = Blueprint('api', __name__) # ==>>a collection of routes, error handlers, etc., that you group together in one file (here, api/routes.py). # Allow CORS requests to this API -CORS(api) +CORS(api) # ==>> you can talk to your flask endpoins whithout the browser blocking the request. -@api.route('/hello', methods=['POST', 'GET']) -def handle_hello(): +@api.route('/places', methods=['POST']) +def get_places_of_drinks(): + data = request.get_json() #==>>Looks at the body of the incoming HTTP request and it parses the JSON text and returns a Python dict (or list) representing that JSON. + latitude = data.get("latitude") + longitude = data.get("longitude") + cocktail = data.get("cocktail") - response_body = { - "message": "Hello! I'm a message that came from the backend, check the network tab on the google inspector and you will see the GET request" - } + if not all[latitude,longitude,cocktail]: #==>> check if all the values are present in the request - return jsonify(response_body), 200 + + + return jsonify({"received": data}), 200 diff --git a/src/app.py b/src/app.py index 0ea8351d5f..691d2f11c5 100644 --- a/src/app.py +++ b/src/app.py @@ -38,7 +38,7 @@ setup_commands(app) # Add all endpoints form the API with a "api" prefix -app.register_blueprint(api, url_prefix='/api') +app.register_blueprint(api, url_prefix='/api') #==>With url_prefix='/api', every route defined in your blueprint (like @api.route('/places') in routes.py) becomes reachable at /api/places. # Handle/serialize errors like a JSON object From 1d74b2397a019c862aa57928d3804939875bc1d8 Mon Sep 17 00:00:00 2001 From: Jackbringas <102544709+Jackbringas@users.noreply.github.com> Date: Sat, 26 Apr 2025 01:21:36 +0000 Subject: [PATCH 06/39] signup done as the figma draft --- src/front/index.css | 4 +++ src/front/pages/signUp.jsx | 68 ++++++++++++++++++++++++++++++++++++++ src/front/routes.jsx | 2 ++ 3 files changed, 74 insertions(+) create mode 100644 src/front/pages/signUp.jsx diff --git a/src/front/index.css b/src/front/index.css index e69de29bb2..c9feee34da 100644 --- a/src/front/index.css +++ b/src/front/index.css @@ -0,0 +1,4 @@ +.logo{ +margin-top: 4rem; +margin-left: 20px; +} \ No newline at end of file diff --git a/src/front/pages/signUp.jsx b/src/front/pages/signUp.jsx new file mode 100644 index 0000000000..7f136caf86 --- /dev/null +++ b/src/front/pages/signUp.jsx @@ -0,0 +1,68 @@ +import React from "react"; + +export const SignUp = () => { + return (
+ +
+
+ Logo +
+
+ + +
+

Sign Up

+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ + + +
+ ); +}; diff --git a/src/front/routes.jsx b/src/front/routes.jsx index 0557df6141..833c32789e 100644 --- a/src/front/routes.jsx +++ b/src/front/routes.jsx @@ -9,6 +9,7 @@ import { Layout } from "./pages/Layout"; import { Home } from "./pages/Home"; import { Single } from "./pages/Single"; import { Demo } from "./pages/Demo"; +import { SignUp } from "./pages/signUp"; export const router = createBrowserRouter( createRoutesFromElements( @@ -25,6 +26,7 @@ export const router = createBrowserRouter( } /> } /> {/* Dynamic route for single items */} } /> + } /> ) ); \ No newline at end of file From 6c56dbd59bde9efb780de786766501e0c324945e Mon Sep 17 00:00:00 2001 From: Wdrew232 Date: Mon, 28 Apr 2025 23:10:30 +0000 Subject: [PATCH 07/39] rendering Search page --- src/front/main.jsx | 10 +++++----- src/front/pages/Search.jsx | 5 +++++ src/front/routes.jsx | 2 ++ 3 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 src/front/pages/Search.jsx diff --git a/src/front/main.jsx b/src/front/main.jsx index a5a3c781dc..e1648f4643 100644 --- a/src/front/main.jsx +++ b/src/front/main.jsx @@ -8,11 +8,11 @@ import { BackendURL } from './components/BackendURL'; const Main = () => { - if(! import.meta.env.VITE_BACKEND_URL || import.meta.env.VITE_BACKEND_URL == "") return ( - - - - ); + // if(! import.meta.env.VITE_BACKEND_URL || import.meta.env.VITE_BACKEND_URL == "") return ( + // + // + // + // ); return ( {/* Provide global state to all components */} diff --git a/src/front/pages/Search.jsx b/src/front/pages/Search.jsx new file mode 100644 index 0000000000..e45c7f14f8 --- /dev/null +++ b/src/front/pages/Search.jsx @@ -0,0 +1,5 @@ +export const Search = () => ( +
+ footer +
+); diff --git a/src/front/routes.jsx b/src/front/routes.jsx index 833c32789e..fd63eedc47 100644 --- a/src/front/routes.jsx +++ b/src/front/routes.jsx @@ -10,6 +10,7 @@ import { Home } from "./pages/Home"; import { Single } from "./pages/Single"; import { Demo } from "./pages/Demo"; import { SignUp } from "./pages/signUp"; +import { Search } from "./pages/Search"; export const router = createBrowserRouter( createRoutesFromElements( @@ -27,6 +28,7 @@ export const router = createBrowserRouter( } /> {/* Dynamic route for single items */} } /> } /> + } /> ) ); \ No newline at end of file From 960fa6e5154f50a4159e9c7e0c8c90f158acf9f4 Mon Sep 17 00:00:00 2001 From: Jackbringas <102544709+Jackbringas@users.noreply.github.com> Date: Mon, 28 Apr 2025 23:50:29 +0000 Subject: [PATCH 08/39] sign in done as figma draft --- src/front/main.jsx | 10 +++---- src/front/pages/signIn.jsx | 53 ++++++++++++++++++++++++++++++++++++++ src/front/routes.jsx | 2 ++ 3 files changed, 60 insertions(+), 5 deletions(-) create mode 100644 src/front/pages/signIn.jsx diff --git a/src/front/main.jsx b/src/front/main.jsx index a5a3c781dc..e1648f4643 100644 --- a/src/front/main.jsx +++ b/src/front/main.jsx @@ -8,11 +8,11 @@ import { BackendURL } from './components/BackendURL'; const Main = () => { - if(! import.meta.env.VITE_BACKEND_URL || import.meta.env.VITE_BACKEND_URL == "") return ( - - - - ); + // if(! import.meta.env.VITE_BACKEND_URL || import.meta.env.VITE_BACKEND_URL == "") return ( + // + // + // + // ); return ( {/* Provide global state to all components */} diff --git a/src/front/pages/signIn.jsx b/src/front/pages/signIn.jsx new file mode 100644 index 0000000000..3913483d21 --- /dev/null +++ b/src/front/pages/signIn.jsx @@ -0,0 +1,53 @@ +import React from "react"; + + export const SignIn = () => { + return (
+ +
+
+ Logo +
+
+ + +
+

Sign In

+ +
+ +
+ + +
+ +
+
+ + + + +
+ ); + }; \ No newline at end of file diff --git a/src/front/routes.jsx b/src/front/routes.jsx index 833c32789e..8549392013 100644 --- a/src/front/routes.jsx +++ b/src/front/routes.jsx @@ -10,6 +10,7 @@ import { Home } from "./pages/Home"; import { Single } from "./pages/Single"; import { Demo } from "./pages/Demo"; import { SignUp } from "./pages/signUp"; +import { SignIn } from "./pages/signIn"; export const router = createBrowserRouter( createRoutesFromElements( @@ -27,6 +28,7 @@ export const router = createBrowserRouter( } /> {/* Dynamic route for single items */} } /> } /> + } /> ) ); \ No newline at end of file From 60d8116b0fb6a314679754547b233e5c100cae61 Mon Sep 17 00:00:00 2001 From: Wdrew232 Date: Tue, 29 Apr 2025 00:53:57 +0000 Subject: [PATCH 09/39] craeted ground work fir search --- src/front/pages/Search.jsx | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/front/pages/Search.jsx b/src/front/pages/Search.jsx index e45c7f14f8..fec7660007 100644 --- a/src/front/pages/Search.jsx +++ b/src/front/pages/Search.jsx @@ -1,5 +1,26 @@ -export const Search = () => ( -
- footer -
-); +import React, { useState } from "react"; + +const handleSearch = (searchItem) => { + fetch("https://www.thecocktaildb.com/api/json/v1/1/search.php?s=" + searchItem, { + method: "GET", + mode: "cors" // fine to include (default is also "cors") + // no custom headers → no CORS pre-flight → no 403/404 + }) + .then(res => res.json()) + .then(data => { + console.log(data); // data.drinks is the array of results + }) + .catch(err => console.error(err)); +}; +export const Search = () => { + const [Search, setSearch] = useState("") + + return ( +
+
+ setSearch(e.target.value)} /> +
+ +
+ ) +} From 40fd3a576905eae9c4d9418b9d57fb41a2b975f9 Mon Sep 17 00:00:00 2001 From: XxDrancerxX Date: Tue, 29 Apr 2025 15:12:14 +0000 Subject: [PATCH 10/39] endpoint built --- src/api/routes.py | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/api/routes.py b/src/api/routes.py index 00017709c9..01e9c7fc75 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -5,6 +5,10 @@ from api.models import db, User from api.utils import generate_sitemap, APIException from flask_cors import CORS +from dotenv import load_dotenv #==>> loads the environment variables from the .env file, pip install python-dotenv +load_dotenv() # reads .env and sets those variables into your environment +import os +from flask import Flask api = Blueprint('api', __name__) # ==>>a collection of routes, error handlers, etc., that you group together in one file (here, api/routes.py). @@ -12,15 +16,31 @@ CORS(api) # ==>> you can talk to your flask endpoins whithout the browser blocking the request. -@api.route('/places', methods=['POST']) +@api.route('/places', methods=['POST']) #==>> this is the endpoint that will be called from the front end def get_places_of_drinks(): data = request.get_json() #==>>Looks at the body of the incoming HTTP request and it parses the JSON text and returns a Python dict (or list) representing that JSON. + print(">> DEBUG data:", data, " type:", type(data)) #==>> print the data received in the request and its type + if not isinstance(data, dict): #==>> check if the data is a dictionary + return jsonify({"error": "Payload must be a JSON object"}), 400 #==>> if the data is not a dictionary, return a 400 error + latitude = data.get("latitude") longitude = data.get("longitude") cocktail = data.get("cocktail") - if not all[latitude,longitude,cocktail]: #==>> check if all the values are present in the request + if not all([latitude,longitude,cocktail]): #==>> check if all the values are present in the request + return jsonify({"errror": "Missing data"}), 400 #==>> return a 400 error if any of the values are missing + + try: + latitude = float(latitude) + longitude = float(longitude) + except (ValueError, TypeError): #==>> check if the values are numbers + + return jsonify({"error": "Latitude and longitude must be numbers"}), 500 #==>> if not, return a 500 error + + GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") #==>> get the google api key from the environment variables + if not GOOGLE_API_KEY: + return jsonify ({"error": "Google API key not found"}), 500 #==>> if the key is not found, return a 500 error + + return jsonify({"received": data}), 200 #==>> return the data received in the request with a 200 status code - - return jsonify({"received": data}), 200 From 977628867f6431fc51233401bf869b773e2a7ec5 Mon Sep 17 00:00:00 2001 From: XxDrancerxX Date: Tue, 29 Apr 2025 18:06:54 +0000 Subject: [PATCH 11/39] continue working on the api --- requirements.txt | 1 + src/api/routes.py | 56 ++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 54 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 4eac45f4f8..39d8350c51 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,3 +24,4 @@ sqlalchemy==1.3.23 urllib3==1.26.3; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4' werkzeug==1.0.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' wtforms==2.3.3 + diff --git a/src/api/routes.py b/src/api/routes.py index 01e9c7fc75..317f0c91d4 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -9,6 +9,11 @@ load_dotenv() # reads .env and sets those variables into your environment import os from flask import Flask +import requests + + + + api = Blueprint('api', __name__) # ==>>a collection of routes, error handlers, etc., that you group together in one file (here, api/routes.py). @@ -19,28 +24,73 @@ @api.route('/places', methods=['POST']) #==>> this is the endpoint that will be called from the front end def get_places_of_drinks(): data = request.get_json() #==>>Looks at the body of the incoming HTTP request and it parses the JSON text and returns a Python dict (or list) representing that JSON. - print(">> DEBUG data:", data, " type:", type(data)) #==>> print the data received in the request and its type + print(">>> Received raw payload:", data) #==>> print the data received from the request if not isinstance(data, dict): #==>> check if the data is a dictionary + print(">>> STEP 2: Bad payload (not a dict)") #==>> print the error message return jsonify({"error": "Payload must be a JSON object"}), 400 #==>> if the data is not a dictionary, return a 400 error - latitude = data.get("latitude") + latitude = data.get("latitude") longitude = data.get("longitude") cocktail = data.get("cocktail") + print(f">>> Parsed fields → cocktail={cocktail!r}, latitude={latitude!r}, longitude={longitude!r}") #==>> print the values of the latitude, longitude and cocktail received from the request if not all([latitude,longitude,cocktail]): #==>> check if all the values are present in the request + print(">>> STEP 4: Missing one of the required fields") return jsonify({"errror": "Missing data"}), 400 #==>> return a 400 error if any of the values are missing try: latitude = float(latitude) longitude = float(longitude) + print(f">>> STEP 5: Coerced lat/lng to floats → {latitude}, {longitude}") except (ValueError, TypeError): #==>> check if the values are numbers + print(">>> STEP 5: Invalid lat/lng, cannot cast to float") return jsonify({"error": "Latitude and longitude must be numbers"}), 500 #==>> if not, return a 500 error GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") #==>> get the google api key from the environment variables if not GOOGLE_API_KEY: + print(">>> STEP 6: Missing Google API key in env") return jsonify ({"error": "Google API key not found"}), 500 #==>> if the key is not found, return a 500 error + + url = "https://maps.googleapis.com/maps/api/place/nearbysearch/json" #==>> url for the google api + params = { #==>> parameters for the google api + "location": f"{latitude},{longitude}", #==>> location of the user + "radius": 5000, + "type": "bar", + "keyword": cocktail, + "key": GOOGLE_API_KEY + + } + print(">>> STEP 7: Calling Google Places with params:", params) + res = requests.get(url, params=params) #==>> make a request to the google api with the parameters + print(">>> STEP 8: Google responded with status", res.status_code) + print(">>> STEP 8b: Google raw body:", res.text[:500], "…") # first 500 chars + if res.status_code != 200: #==>> check if the request was successful + return jsonify({ + "error": "Failed to fetch data from Google", + "details": res.text + }), 500 + places = res.json().get("results",[]) #==>> get the results from the response + print(f">>> STEP 9: Google returned {len(places)} results") + filtered_places = [] + for place in places: + if place.get("business_status") != "OPERATIONAL": + continue + + filtered_places.append({ + "name": place.get("name"), + "address": place.get("vicinity") or place.get("formatted_address"), + "rating": place.get("rating"), + "user_ratings_total":place.get("user_ratings_total"), + "location": place["geometry"]["location"], + "place_id": place.get("place_id"), + "photo_reference": place.get("photos", [{}])[0].get("photo_reference") + }) + print(f">>> STEP 10: Filtered down to {len(filtered_places)} operational bars") + print(">>> STEP 11: Returning to client:", filtered_places) + - return jsonify({"received": data}), 200 #==>> return the data received in the request with a 200 status code + return jsonify( filtered_places), 200 #==>> return the filtered places as a json object with a 200 status code + From 34da9da0a069ec9d29150a47b9d2ce4897549865 Mon Sep 17 00:00:00 2001 From: XxDrancerxX Date: Wed, 30 Apr 2025 18:02:29 +0000 Subject: [PATCH 12/39] checking 3 endpoints --- src/api/routes.py | 285 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 223 insertions(+), 62 deletions(-) diff --git a/src/api/routes.py b/src/api/routes.py index 317f0c91d4..52675243a4 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -1,96 +1,257 @@ """ This module takes care of starting the API Server, Loading the DB and Adding the endpoints """ +import requests +import os from flask import Flask, request, jsonify, url_for, Blueprint from api.models import db, User from api.utils import generate_sitemap, APIException from flask_cors import CORS -from dotenv import load_dotenv #==>> loads the environment variables from the .env file, pip install python-dotenv +# ==>> loads the environment variables from the .env file, pip install python-dotenv +from dotenv import load_dotenv load_dotenv() # reads .env and sets those variables into your environment -import os -from flask import Flask -import requests - - - -api = Blueprint('api', __name__) # ==>>a collection of routes, error handlers, etc., that you group together in one file (here, api/routes.py). +# ==>>a collection of routes, error handlers, etc., that you group together in one file (here, api/routes.py). +api = Blueprint('api', __name__) # Allow CORS requests to this API -CORS(api) # ==>> you can talk to your flask endpoins whithout the browser blocking the request. +# ==>> you can talk to your flask endpoins whithout the browser blocking the request. +CORS(api) -@api.route('/places', methods=['POST']) #==>> this is the endpoint that will be called from the front end -def get_places_of_drinks(): - data = request.get_json() #==>>Looks at the body of the incoming HTTP request and it parses the JSON text and returns a Python dict (or list) representing that JSON. - print(">>> Received raw payload:", data) #==>> print the data received from the request - if not isinstance(data, dict): #==>> check if the data is a dictionary - print(">>> STEP 2: Bad payload (not a dict)") #==>> print the error message - return jsonify({"error": "Payload must be a JSON object"}), 400 #==>> if the data is not a dictionary, return a 400 error - - latitude = data.get("latitude") - longitude = data.get("longitude") - cocktail = data.get("cocktail") - print(f">>> Parsed fields → cocktail={cocktail!r}, latitude={latitude!r}, longitude={longitude!r}") #==>> print the values of the latitude, longitude and cocktail received from the request - - if not all([latitude,longitude,cocktail]): #==>> check if all the values are present in the request - print(">>> STEP 4: Missing one of the required fields") - return jsonify({"errror": "Missing data"}), 400 #==>> return a 400 error if any of the values are missing +# ==>> this is the endpoint that will be called from the front end +# ==>> Search Places by coordinates: Accepts E.G: { latitude: 40.75, longitude: -73.99, cocktail: "Mojito" } +@api.route('/places', methods=['POST']) +def get_places_of_drinks(): + GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") # ==>> get the google api key from the environment variables + data = request.get_json()# ==>>Looks at the body of the incoming HTTP request and it parses the JSON text and returns a Python dict (or list) representing that JSON. + if not isinstance(data, dict): # ==>> check if the data is a dictionary + return jsonify({"error": "Payload must be a JSON object"}), 400 # ==>> if the data is not a dictionary, return a 400 error + + if not GOOGLE_API_KEY: + return jsonify({"error": "Google API key not found"}), 500 # ==>> if the key is not found, return a 500 error - try: + page_token = data.get("next_page_token") # ==>> get the page token from the request that is used for pagination. + url = "https://maps.googleapis.com/maps/api/place/nearbysearch/json" # ==>> url for the google api + + if page_token: # ==>> check if the page token is present in the request # We’re loading a “next” page + params = { + "pagetoken": page_token, # ==>> pass the page token to the request + "key": GOOGLE_API_KEY # ==>> get the google api key from the environment variables + } + else: + opennow = data.get("opennow") + latitude = data.get("latitude") + longitude = data.get("longitude") + cocktail = data.get("cocktail") + + + if not all([latitude, longitude, cocktail ]): # ==>> check if all the values are present in the request + return jsonify({"error": "Missing data"}), 400 # ==>> return a 400 error if any of the values are missing + try: latitude = float(latitude) longitude = float(longitude) - print(f">>> STEP 5: Coerced lat/lng to floats → {latitude}, {longitude}") - except (ValueError, TypeError): #==>> check if the values are numbers - print(">>> STEP 5: Invalid lat/lng, cannot cast to float") - - return jsonify({"error": "Latitude and longitude must be numbers"}), 500 #==>> if not, return a 500 error - - GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") #==>> get the google api key from the environment variables - if not GOOGLE_API_KEY: - print(">>> STEP 6: Missing Google API key in env") - return jsonify ({"error": "Google API key not found"}), 500 #==>> if the key is not found, return a 500 error - - url = "https://maps.googleapis.com/maps/api/place/nearbysearch/json" #==>> url for the google api - params = { #==>> parameters for the google api - "location": f"{latitude},{longitude}", #==>> location of the user - "radius": 5000, + + except (ValueError, TypeError): # ==>> check if the values are numbers + return jsonify({"error": "Latitude and longitude must be numbers"}), 400 # ==>> if not, return a 400 error + # First page: use location, radius, keyword… + params = { # ==>> parameters for the google api + "location": f"{latitude},{longitude}", # ==>> location of the user + "radius": 5000, "type": "bar", "keyword": cocktail, - "key": GOOGLE_API_KEY - - } - print(">>> STEP 7: Calling Google Places with params:", params) - res = requests.get(url, params=params) #==>> make a request to the google api with the parameters - print(">>> STEP 8: Google responded with status", res.status_code) - print(">>> STEP 8b: Google raw body:", res.text[:500], "…") # first 500 chars - if res.status_code != 200: #==>> check if the request was successful + "key": GOOGLE_API_KEY + } + if opennow: # ==>> check if the opennow parameter is present in the request + params["opennow"] = "true" # ==>> add the opennow parameter to the request + + res = requests.get(url, params=params) # ==>> make a request to the google api with the parameters + if res.status_code != 200: # ==>> check if the request was successful return jsonify({ "error": "Failed to fetch data from Google", - "details": res.text - }), 500 - places = res.json().get("results",[]) #==>> get the results from the response - print(f">>> STEP 9: Google returned {len(places)} results") + "details": res.text + }), 500 + body = res.json() + places = body.get("results", []) + next_page_token = body.get("next_page_token") + + filtered_places = [] for place in places: if place.get("business_status") != "OPERATIONAL": - continue - + continue + ref = place.get("photos", [{}])[0].get("photo_reference") # ==>> ↓↓↓get the photo reference of the place.↓↓↓ This accesses the "photos" key in the place dictionary, which is a list of dictionaries. ↓↓↓It tries to get the first dictionary in that list (or an empty dictionary if the list is empty) and then accesses the "photo_reference" key. + # ==>> check if the photo reference is not empty ## ↑↑↑one representative image per place,↑↑↑ and the first one is usually the best (it’s what Google thinks is most relevant)↑↑↑ + if ref: + photo_url = ( + f"https://maps.googleapis.com/maps/api/place/photo" # ==>> url for the google api + f"?maxwidth=400" + f"&photoreference={ref}" # ==>> photo reference of the place + f"&key={GOOGLE_API_KEY}" # ==>> google api key + ) + else: + photo_url = None + filtered_places.append({ + "name": place.get("name"), + "address": place.get("vicinity") or place.get("formatted_address"), # ==>> get the address of the place, First, it tries to get the "vicinity" key (a nearby address). If "vicinity" is not available, it falls back to "formatted_address" (the full address). + "rating": place.get("rating"), + "location": place["geometry"]["location"], # ==>> get the location of the place This accesses the "geometry" key in the place dictionary, which contains a "location" key. "location" is another dictionary with latitude and longitude values. + "user_ratings_total": place.get("user_ratings_total"), + "place_id": place.get("place_id"), + "photo_url": photo_url + + }) + return jsonify({ + "places": filtered_places, + "next_page_token": next_page_token + }), 200 # ==>> return the filtered places as a json object with a 200 status code + + + +# ==>> Search Places in a specific location:Accepts E.G: { zip_code: "10001" } Returns { latitude: 40.75, longitude: -73.99} +@api.route('/places/by-location', methods=['POST']) +def get_places_by_location(): + data = request.get_json() + GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") + print(">>> [by-location] STEP 1: data =", data, " type:", type(data)) + if not isinstance(data, dict): + return jsonify({"error": "Data must be a JSON object"}), 400 + if not GOOGLE_API_KEY: + return jsonify({"error": "Google API key not found"}), 500 + + page_token = data.get("next_page_token") + filtered_places = [] + + if page_token: # ==>> check if the page token is present in the request # We’re loading a “next” page + places_url = "https://maps.googleapis.com/maps/api/place/nearbysearch/json" # ==>> url for the google api + places_params = { + "pagetoken": page_token, # ==>> pass the page token to the request + "key": GOOGLE_API_KEY # ==>> get the google api key from the environment variables + } + places_res = requests.get(places_url, params=places_params) # ==>> make a request to the google api with the parameters + if places_res.status_code != 200: # ==>> check if the request was successful + return jsonify({"error": "Failed to fetch data from Google", "details": places_res.text}), 500 + body = places_res.json() # ==>> parse the response as json + raw_places = body.get("results", []) + next_page_token = body.get("next_page_token") + + else: + location = data.get("location") + cocktail = data.get("cocktail") + print(">>> [by-location] STEP 2: location, cocktail =", location, cocktail) + + if not all([location, cocktail]): # ==>> check if all the values are present in the request + print(">>> [by-location] Missing location or cocktail") + return jsonify({"error": "Missing data"}), 400 # ==>> return a 400 error if any of the values are missing + + geo_url = "https://maps.googleapis.com/maps/api/geocode/json" # ==>> Call Geocoding API + geo_params = { + "address": location, + "key": GOOGLE_API_KEY + } + geo_res = requests.get(geo_url, params=geo_params) + print(">>> [by-location] STEP 3b: geocode status =", geo_res.status_code) + print(">>> [by-location] geocode body snippet:", geo_res.text[:200], "…") + + if geo_res.status_code != 200: # ==>> check if the request was successful + return jsonify({"error": "Failed to fetch data from Google Geocoding API"}), 500 # ==>> return a 500 error if the request failed + geo_data = geo_res.json() # ==>> # parse JSON into a dict + results = geo_data.get("results", []) # ==>> get the results from the response + if not results: + return jsonify({"error": "No results found"}), 404 # ==>> return a 404 error if there are no results + + location_data = results[0]["geometry"]["location"] # ==>> get the location data from the first result + lat, lng = location_data["lat"], location_data["lng"] # ==>> extract latitude and longitude + print(f">>> [by-location] STEP 4: resolved '{location_data}' → lat={lat}, lng={lng}") + + places_url = "https://maps.googleapis.com/maps/api/place/nearbysearch/json" # Build Nearby Search request + places_params = { + "location": f"{lat},{lng}", + "radius": 5000, + "type": "bar", + "keyword": cocktail, + "key": GOOGLE_API_KEY + } + + if data.get("opennow"): # ==>> check if the opennow parameter is present in the request + places_params["opennow"] = "true" # ==>> add the opennow parameter to the request + print(">>> [by-location] STEP 5: places params =", places_params) + + places_res = requests.get(places_url, params=places_params) + print(">>> [by-location] STEP 5b: places status =", places_res.status_code) + print(">>> [by-location] places body snippet:", places_res.text[:200], "…") + if places_res.status_code != 200: + return jsonify({"error": "Failed to fetch data from Google Places API", "details": places_res.text}), 500 + body = places_res.json() + raw_places = body.get("results", []) + next_page_token = body.get("next_page_token") + print(">>> [by-location] STEP 6: received", len(raw_places), "raw places") + + for place in raw_places: + if place.get("business_status") != "OPERATIONAL": + continue + ref = place.get("photos", [{}])[0].get("photo_reference") # ==>> ↓↓↓get the photo reference of the place.↓↓↓ This accesses the "photos" key in the place dictionary, which is a list of dictionaries. ↓↓↓It tries to get the first dictionary in that list (or an empty dictionary if the list is empty) and then accesses the "photo_reference" key. + if ref: # ==>> check if the photo reference is not empty ## ↑↑↑one representative image per place,↑↑↑ and the first one is usually the best (it’s what Google thinks is most relevant)↑↑↑ + photo_url = ( + f"https://maps.googleapis.com/maps/api/place/photo" # ==>> url for the google api + f"?maxwidth=400" + f"&photoreference={ref}" # ==>> photo reference of the place + f"&key={GOOGLE_API_KEY}" # ==>> google api key + ) + else: + photo_url = None + + filtered_places.append( + { "name": place.get("name"), "address": place.get("vicinity") or place.get("formatted_address"), "rating": place.get("rating"), - "user_ratings_total":place.get("user_ratings_total"), + "user_ratings_total": place.get("user_ratings_total"), "location": place["geometry"]["location"], "place_id": place.get("place_id"), - "photo_reference": place.get("photos", [{}])[0].get("photo_reference") + "photo_url": photo_url }) - print(f">>> STEP 10: Filtered down to {len(filtered_places)} operational bars") - print(">>> STEP 11: Returning to client:", filtered_places) + print(f">>> [by-location] STEP 7: filtered down to {len(filtered_places)} bars") + return jsonify({ + "places": filtered_places, + "next_page_token": next_page_token + }), 200 + +@api.route('/places/details', methods=['POST']) # ==>> Endpoint for the Place Details API within the Google Maps Platform. It allows you to request detailed information about a specific place, such as a business or point of interest. +def get_place_details(): + GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") # ==>> get the google api key from the environment variables + if not GOOGLE_API_KEY: # ==>> check if the google api key is present + return jsonify({"error": "Google API key not found"}), 500 # ==>> return a 500 error if the google api key is missing + data = request.get_json() # ==>> get the data from the request + place_id = data.get("place_id") # ==>> get the place id from the request - - return jsonify( filtered_places), 200 #==>> return the filtered places as a json object with a 200 status code - + if not place_id: # ==>> check if the place id is present in the request + return jsonify({"error": "Missing place_id"}), 400 # ==>> return a 400 error if the place id is missing + + url = "https://maps.googleapis.com/maps/api/place/details/json" # ==>> url for the google api + params = { + "place_id": place_id, + "fields":"name,formatted_phone_number,opening_hours,website,reviews", + "key": GOOGLE_API_KEY + } + res = requests.get(url, params=params) # ==>> make a request to the google api with the parameters + if res.status_code != 200: # ==>> check if the request was successful + return jsonify({"error": "Failed to fetch data from Google", "details": res.text}), 500 # ==>> return a 500 error if the request failed + + body = res.json() # ==>> parse the response as json + details = body.get("result", {}) # Grab the “result” object from the Google response (or an empty dict if it’s missing) + if not details: # ==>> check if the details are present in the response + return jsonify({"error": "No details found"}), 404 # ==>> return a 404 error if the details are missing + + return jsonify({ + "name": details.get("name"), + "formatted_address": details.get("formatted_address"), + "formatted_phone_number": details.get("formatted_phone_number", "N/A"), + "opening_hours": details.get("opening_hours", {}), + "website": details.get("website"), + "reviews": details.get("reviews", []) +}), 200 From bdd56134496612b9b6770e1c0646b37ad9787637 Mon Sep 17 00:00:00 2001 From: XxDrancerxX Date: Wed, 30 Apr 2025 23:13:04 +0000 Subject: [PATCH 13/39] Add requests, flask-cors, python-dotenv deps --- Pipfile | 5 +- Pipfile.lock | 144 ++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 134 insertions(+), 15 deletions(-) diff --git a/Pipfile b/Pipfile index 44e04f14ff..834a2dde60 100644 --- a/Pipfile +++ b/Pipfile @@ -11,8 +11,6 @@ flask-sqlalchemy = "*" flask-migrate = "*" flask-swagger = "*" psycopg2-binary = "*" -python-dotenv = "*" -flask-cors = "*" gunicorn = "*" cloudinary = "*" flask-admin = "*" @@ -20,6 +18,9 @@ typing-extensions = "*" flask-jwt-extended = "==4.6.0" wtforms = "==3.1.2" sqlalchemy = "*" +requests = "*" +flask-cors = "*" +python-dotenv = "*" [requires] python_version = "3.13" diff --git a/Pipfile.lock b/Pipfile.lock index b201c3decc..0566464e5b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d2e672e650278aeeee2fe49bd76d76497d8b65a50f8b5dbb121d265cbc6ef4e5" + "sha256": "bf10612551b9927f533b121b086761c613604aa20a5af737364fcdf45ecb33e0" }, "pipfile-spec": 6, "requires": { @@ -34,11 +34,109 @@ }, "certifi": { "hashes": [ - "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", - "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe" + "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", + "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3" ], "markers": "python_version >= '3.6'", - "version": "==2025.1.31" + "version": "==2025.4.26" + }, + "charset-normalizer": { + "hashes": [ + "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", + "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa", + "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a", + "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", + "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b", + "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", + "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", + "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", + "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", + "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", + "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", + "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", + "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", + "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", + "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", + "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", + "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", + "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", + "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", + "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", + "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e", + "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a", + "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4", + "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca", + "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", + "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", + "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", + "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", + "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", + "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", + "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", + "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", + "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", + "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", + "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", + "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd", + "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c", + "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", + "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", + "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", + "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", + "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824", + "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", + "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf", + "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487", + "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d", + "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd", + "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", + "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534", + "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", + "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", + "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", + "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd", + "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", + "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9", + "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", + "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", + "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d", + "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", + "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", + "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", + "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", + "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", + "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", + "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8", + "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", + "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", + "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", + "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", + "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", + "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", + "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", + "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", + "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", + "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", + "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", + "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", + "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e", + "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6", + "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", + "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", + "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e", + "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", + "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", + "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c", + "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", + "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", + "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089", + "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", + "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e", + "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", + "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.1" }, "click": { "hashes": [ @@ -62,6 +160,7 @@ "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136" ], "index": "pypi", + "markers": "python_version >= '3.9'", "version": "==3.1.0" }, "flask-admin": { @@ -78,6 +177,7 @@ "sha256:fa5cb364ead54bbf401a26dbf03030c6b18fb2fcaf70408096a572b409586b0c" ], "index": "pypi", + "markers": "python_version >= '3.9' and python_version < '4.0'", "version": "==5.0.1" }, "flask-jwt-extended": { @@ -199,6 +299,14 @@ "index": "pypi", "version": "==23.0.0" }, + "idna": { + "hashes": [ + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" + ], + "markers": "python_version >= '3.6'", + "version": "==3.10" + }, "itsdangerous": { "hashes": [ "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", @@ -209,11 +317,11 @@ }, "jinja2": { "hashes": [ - "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", - "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb" + "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", + "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67" ], "markers": "python_version >= '3.7'", - "version": "==3.1.5" + "version": "==3.1.6" }, "mako": { "hashes": [ @@ -382,11 +490,12 @@ }, "python-dotenv": { "hashes": [ - "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", - "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a" + "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", + "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d" ], "index": "pypi", - "version": "==1.0.1" + "markers": "python_version >= '3.9'", + "version": "==1.1.0" }, "pyyaml": { "hashes": [ @@ -447,6 +556,15 @@ "markers": "python_version >= '3.8'", "version": "==6.0.2" }, + "requests": { + "hashes": [ + "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", + "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.32.3" + }, "six": { "hashes": [ "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", @@ -528,11 +646,11 @@ }, "urllib3": { "hashes": [ - "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", - "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d" + "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", + "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813" ], "markers": "python_version >= '3.9'", - "version": "==2.3.0" + "version": "==2.4.0" }, "werkzeug": { "hashes": [ From 096932ac1be3e97d8878979eaeb44ea5279d5168 Mon Sep 17 00:00:00 2001 From: Jackbringas <102544709+Jackbringas@users.noreply.github.com> Date: Wed, 30 Apr 2025 23:22:49 +0000 Subject: [PATCH 14/39] password recovery view added --- src/front/pages/password.jsx | 67 ++++++++++++++++++++++++++++++++++++ src/front/routes.jsx | 5 ++- 2 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 src/front/pages/password.jsx diff --git a/src/front/pages/password.jsx b/src/front/pages/password.jsx new file mode 100644 index 0000000000..626749e05e --- /dev/null +++ b/src/front/pages/password.jsx @@ -0,0 +1,67 @@ +import React from "react"; + +export const Password = () => { + return (
+ +
+
+ Logo +
+
+ + +
+

Password Recovery

+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ + + +
+ ); +}; diff --git a/src/front/routes.jsx b/src/front/routes.jsx index 8dbd91e3c3..70f9b174c8 100644 --- a/src/front/routes.jsx +++ b/src/front/routes.jsx @@ -14,7 +14,7 @@ import { SignUp } from "./pages/signUp"; import { Search } from "./pages/Search"; import { SignIn } from "./pages/signIn"; - +import { Password } from "./pages/password"; export const router = createBrowserRouter( createRoutesFromElements( @@ -32,10 +32,9 @@ export const router = createBrowserRouter( } /> {/* Dynamic route for single items */} } /> } /> - } /> - } /> + } /> ) From 331459ae4a6490f0f52e236d152d6a620bb2d14d Mon Sep 17 00:00:00 2001 From: Wdrew232 Date: Wed, 30 Apr 2025 23:55:18 +0000 Subject: [PATCH 15/39] ground floor of custom drinks --- src/api/models.py | 19 ++++++ src/front/index.css | 38 ++++++++++-- src/front/pages/Custom.jsx | 123 +++++++++++++++++++++++++++++++++++++ src/front/routes.jsx | 30 ++++----- 4 files changed, 192 insertions(+), 18 deletions(-) create mode 100644 src/front/pages/Custom.jsx diff --git a/src/api/models.py b/src/api/models.py index da515f6a1a..ee1f4b54a8 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -10,6 +10,25 @@ class User(db.Model): password: Mapped[str] = mapped_column(nullable=False) is_active: Mapped[bool] = mapped_column(Boolean(), nullable=False) +class ingredients(db.Model): + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) + item: Mapped[str] = mapped_column(nullable=False) + item: Mapped[str] = mapped_column(nullable=False) + item: Mapped[str] = mapped_column(nullable=False) + item: Mapped[str] = mapped_column(nullable=False) + +class Custom(db.Model): + id: Mapped[int] = mapped_column(primary_key=True) + email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) + password: Mapped[str] = mapped_column(nullable=False) + is_active: Mapped[bool] = mapped_column(Boolean(), nullable=False) + + # "strIngredient1": "Tequila",whiskey,vodka,gin,bourben,brandy + # "strIngredient2": "Triple sec",agave,bitters,grand marnie, + # + # "strIngredient4": "Salt",sugar,,lemon,lime,pinapple, + def serialize(self): return { diff --git a/src/front/index.css b/src/front/index.css index c9feee34da..92012774b7 100644 --- a/src/front/index.css +++ b/src/front/index.css @@ -1,4 +1,34 @@ -.logo{ -margin-top: 4rem; -margin-left: 20px; -} \ No newline at end of file +.logo { + margin-top: 4rem; + margin-left: 20px; +} + +.modal { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: white; + padding: 20px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + border-radius: 8px; + z-index: 1000; +} + +.modal h2 { + margin-bottom: 20px; +} + +.modal button { + margin: 10px; + padding: 10px 15px; + border: none; + background: #007bff; + color: white; + cursor: pointer; + border-radius: 5px; +} + +.modal button:hover { + background: #0056b3; +} diff --git a/src/front/pages/Custom.jsx b/src/front/pages/Custom.jsx new file mode 100644 index 0000000000..918aa6b417 --- /dev/null +++ b/src/front/pages/Custom.jsx @@ -0,0 +1,123 @@ +import React, { useState, useEffect } from "react"; + +const handleFetch = (setDrinks) => { + fetch("https://www.thecocktaildb.com/api/json/v1/1/list.php?i=list") + .then((res) => res.json()) + .then((data) => { + if (data.drinks) { + setDrinks(data.drinks); + } else { + setDrinks([]); + } + }) + .catch((err) => console.error(err)); +}; + +export const Custom = () => { + const [drinks, setDrinks] = useState([]); + const [ingredients, setIngredients] = useState([]); + const [selectedIngredients, setSelectedIngredients] = useState([]); + const [matches, setMatches] = useState(0); + const [isModalOpen, setIsModalOpen] = useState(false); + + useEffect(() => { + // Fetch cocktails on app load + handleFetch(setDrinks); + // Extract ingredients from fetched data + const extractIngredients = (cocktails) => { + const allIngredients = cocktails.reduce((acc, drink) => { + [...Array(15).keys()].forEach((i) => { + const ingredient = drink[`strIngredient${i + 1}`]; + if (ingredient && !acc.includes(ingredient)) { + acc.push(ingredient); + } + }); + return acc; + }, []); + setIngredients(allIngredients); + }; + handleFetch((cocktails) => { + extractIngredients(cocktails); + }); + }, []); + + const handleIngredientToggle = (ingredient) => { + const newSelected = selectedIngredients.includes(ingredient) + ? selectedIngredients.filter((ing) => ing !== ingredient) + : [...selectedIngredients, ingredient]; + + setSelectedIngredients(newSelected); + + // Calculate matches + const matchCount = drinks.filter((drink) => + newSelected.every((ing) => { + return [...Array(15).keys()] + .map((i) => drink[`strIngredient${i + 1}`]) + .includes(ing); + }) + ).length; + setMatches(matchCount); + }; + + const saveCustomSet = () => { + const customSet = { + name: `Custom Set ${new Date().toISOString()}`, + ingredients: selectedIngredients, + }; + const savedSets = JSON.parse(localStorage.getItem("customSets")) || []; + localStorage.setItem("customSets", JSON.stringify([...savedSets, customSet])); + setIsModalOpen(false); + }; + console.log("drinks!!!", drinks) + return ( +
+ + + {/* Cocktail list */} +
+ {drinks.length > 0 && drinks && drinks !== "no data found" ? + drinks.map((drink) => ( +
+

{drink.strIngredient1}

+ {/* {drink.strDrink} +

Glass: {drink.strGlass}

+

Category: {drink.strCategory}

+

Ingredients:

+
    + {[...Array(15).keys()].map((i) => { + const ingredient = drink[`strIngredient${i + 1}`]; + return ingredient &&
  • {ingredient}
  • ; + })} +
+

Instructions: {drink.strInstructions}

*/} +
+ ) + ) + : + "No Drinks availabale!!!" + + } +
+ + {/* Modal for My Ingredients */} + {isModalOpen && ( +
+

Select Your Ingredients

+ {ingredients.map((ingredient) => ( +
+ handleIngredientToggle(ingredient)} + /> + +
+ ))} +
Matches: {matches} cocktails
+ + +
+ )} +
+ ); +}; diff --git a/src/front/routes.jsx b/src/front/routes.jsx index fd63eedc47..e43bb266c4 100644 --- a/src/front/routes.jsx +++ b/src/front/routes.jsx @@ -1,9 +1,9 @@ // Import necessary components and functions from react-router-dom. import { - createBrowserRouter, - createRoutesFromElements, - Route, + createBrowserRouter, + createRoutesFromElements, + Route, } from "react-router-dom"; import { Layout } from "./pages/Layout"; import { Home } from "./pages/Home"; @@ -11,24 +11,26 @@ import { Single } from "./pages/Single"; import { Demo } from "./pages/Demo"; import { SignUp } from "./pages/signUp"; import { Search } from "./pages/Search"; +import { Custom } from "./pages/Custom"; export const router = createBrowserRouter( - createRoutesFromElements( + createRoutesFromElements( // CreateRoutesFromElements function allows you to build route elements declaratively. // Create your routes here, if you want to keep the Navbar and Footer in all views, add your new routes inside the containing Route. // Root, on the contrary, create a sister Route, if you have doubts, try it! // Note: keep in mind that errorElement will be the default page when you don't get a route, customize that page to make your project more attractive. // Note: The child paths of the Layout element replace the Outlet component with the elements contained in the "element" attribute of these child paths. - // Root Route: All navigation will start from here. - } errorElement={

Not found!

} > + // Root Route: All navigation will start from here. + } errorElement={

Not found!

} > - {/* Nested Routes: Defines sub-routes within the BaseHome component. */} - } /> - } /> {/* Dynamic route for single items */} - } /> - } /> - } /> - - ) + {/* Nested Routes: Defines sub-routes within the BaseHome component. */} + } /> + } /> {/* Dynamic route for single items */} + } /> + } /> + } /> + } /> + + ) ); \ No newline at end of file From e83e89c69f7c071bcf35741622f78f35a3776217 Mon Sep 17 00:00:00 2001 From: XxDrancerxX Date: Thu, 1 May 2025 00:12:51 +0000 Subject: [PATCH 16/39] basic layout --- src/api/routes.py | 2 ++ src/front/components/Navbar.jsx | 5 +++++ src/front/pages/GoogleApi.jsx | 22 ++++++++++++++++++++++ src/front/routes.jsx | 5 ++--- 4 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 src/front/pages/GoogleApi.jsx diff --git a/src/api/routes.py b/src/api/routes.py index 52675243a4..98ab2d1e8e 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -109,6 +109,7 @@ def get_places_of_drinks(): }), 200 # ==>> return the filtered places as a json object with a 200 status code + # ==>> Search Places in a specific location:Accepts E.G: { zip_code: "10001" } Returns { latitude: 40.75, longitude: -73.99} @api.route('/places/by-location', methods=['POST']) @@ -219,6 +220,7 @@ def get_places_by_location(): "next_page_token": next_page_token }), 200 + @api.route('/places/details', methods=['POST']) # ==>> Endpoint for the Place Details API within the Google Maps Platform. It allows you to request detailed information about a specific place, such as a business or point of interest. def get_place_details(): GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") # ==>> get the google api key from the environment variables diff --git a/src/front/components/Navbar.jsx b/src/front/components/Navbar.jsx index 30d43a2636..b9e5cce508 100644 --- a/src/front/components/Navbar.jsx +++ b/src/front/components/Navbar.jsx @@ -13,6 +13,11 @@ export const Navbar = () => {
+
+ + + +
); diff --git a/src/front/pages/GoogleApi.jsx b/src/front/pages/GoogleApi.jsx new file mode 100644 index 0000000000..66abcf5efb --- /dev/null +++ b/src/front/pages/GoogleApi.jsx @@ -0,0 +1,22 @@ +import { Link } from "react-router-dom"; +import useGlobalReducer from "../hooks/useGlobalReducer"; + +export const GoogleApi = () => { + const { store, dispatch } = useGlobalReducer() + + return ( +
{/*ensures your element is at least as tall as the viewport—if your content is taller, it will grow and scroll. */} +
{/* Ensures the row takes up the full height of its parent container */} +
aaa
+
aaa
+ + +
+ + + + + +
+ ); + }; diff --git a/src/front/routes.jsx b/src/front/routes.jsx index 8dbd91e3c3..2e98c35004 100644 --- a/src/front/routes.jsx +++ b/src/front/routes.jsx @@ -10,7 +10,7 @@ import { Home } from "./pages/Home"; import { Single } from "./pages/Single"; import { Demo } from "./pages/Demo"; import { SignUp } from "./pages/signUp"; - +import { GoogleApi } from "./pages/GoogleApi"; import { Search } from "./pages/Search"; import { SignIn } from "./pages/signIn"; @@ -32,10 +32,9 @@ export const router = createBrowserRouter( } /> {/* Dynamic route for single items */} } /> } /> - } /> - } /> + } /> ) From 3267d8dab483795762b87b25450702e5027a5326 Mon Sep 17 00:00:00 2001 From: Wdrew232 Date: Thu, 1 May 2025 00:38:12 +0000 Subject: [PATCH 17/39] added styling --- src/front/index.css | 70 ++++++++++++++++++++++++++++ src/front/pages/Custom.jsx | 95 +++++++++++++++----------------------- 2 files changed, 106 insertions(+), 59 deletions(-) diff --git a/src/front/index.css b/src/front/index.css index 92012774b7..356d0152bf 100644 --- a/src/front/index.css +++ b/src/front/index.css @@ -32,3 +32,73 @@ .modal button:hover { background: #0056b3; } +body { + margin: 0; + padding: 0; + background: linear-gradient(135deg, #a1c4fd, #c2e9fb); /* Gradient background */ + font-family: 'Arial', sans-serif; + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; /* Full screen height */ + color: #333; /* Text color */ +} + +h1, h2, h3 { + font-family: 'Helvetica', sans-serif; + color: #444; /* Darker text color for headers */ +} + +button { + background-color: #007bff; /* Button background */ + color: #fff; /* Button text */ + border: none; + border-radius: 5px; + padding: 10px 15px; + cursor: pointer; + font-size: 1rem; + transition: background-color 0.3s, transform 0.3s; +} + +button:hover { + background-color: #0056b3; /* Hover background */ + transform: scale(1.05); /* Slight zoom on hover */ +} +.Cocktail { + display: flex; + flex-wrap: wrap; + gap: 20px; + justify-content: center; +} + +.drink { + width: 150px; + padding: 10px; + border: 1px solid #ddd; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + text-align: center; + background: white; + transition: transform 0.2s; +} + +.drink img { + border-radius: 50%; + margin-bottom: 10px; +} + +.drink div { + margin-top: 10px; + display: flex; + flex-direction: column; + align-items: center; +} + +.drink input[type="checkbox"] { + margin-bottom: 5px; +} + +.drink:hover { + transform: scale(1.05); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); +} diff --git a/src/front/pages/Custom.jsx b/src/front/pages/Custom.jsx index 918aa6b417..1202f1a3d6 100644 --- a/src/front/pages/Custom.jsx +++ b/src/front/pages/Custom.jsx @@ -1,44 +1,32 @@ import React, { useState, useEffect } from "react"; -const handleFetch = (setDrinks) => { +const handleFetch = (setIngredients) => { fetch("https://www.thecocktaildb.com/api/json/v1/1/list.php?i=list") .then((res) => res.json()) .then((data) => { if (data.drinks) { - setDrinks(data.drinks); + // Add placeholder images for each ingredient + const ingredientsWithImages = data.drinks.map((drink) => ({ + ...drink, + image: `https://www.thecocktaildb.com/images/ingredients/${drink.strIngredient1}-Medium.png`, + })); + setIngredients(ingredientsWithImages); } else { - setDrinks([]); + setIngredients([]); } }) .catch((err) => console.error(err)); }; export const Custom = () => { - const [drinks, setDrinks] = useState([]); const [ingredients, setIngredients] = useState([]); const [selectedIngredients, setSelectedIngredients] = useState([]); const [matches, setMatches] = useState(0); const [isModalOpen, setIsModalOpen] = useState(false); useEffect(() => { - // Fetch cocktails on app load - handleFetch(setDrinks); - // Extract ingredients from fetched data - const extractIngredients = (cocktails) => { - const allIngredients = cocktails.reduce((acc, drink) => { - [...Array(15).keys()].forEach((i) => { - const ingredient = drink[`strIngredient${i + 1}`]; - if (ingredient && !acc.includes(ingredient)) { - acc.push(ingredient); - } - }); - return acc; - }, []); - setIngredients(allIngredients); - }; - handleFetch((cocktails) => { - extractIngredients(cocktails); - }); + // Fetch ingredients with images when the component mounts + handleFetch(setIngredients); }, []); const handleIngredientToggle = (ingredient) => { @@ -47,16 +35,6 @@ export const Custom = () => { : [...selectedIngredients, ingredient]; setSelectedIngredients(newSelected); - - // Calculate matches - const matchCount = drinks.filter((drink) => - newSelected.every((ing) => { - return [...Array(15).keys()] - .map((i) => drink[`strIngredient${i + 1}`]) - .includes(ing); - }) - ).length; - setMatches(matchCount); }; const saveCustomSet = () => { @@ -68,49 +46,48 @@ export const Custom = () => { localStorage.setItem("customSets", JSON.stringify([...savedSets, customSet])); setIsModalOpen(false); }; - console.log("drinks!!!", drinks) + return (
- {/* Cocktail list */} + {/* Ingredient list (cards for each ingredient) */}
- {drinks.length > 0 && drinks && drinks !== "no data found" ? - drinks.map((drink) => ( + {ingredients.length > 0 + ? ingredients.map((drink) => (

{drink.strIngredient1}

- {/* {drink.strDrink} -

Glass: {drink.strGlass}

-

Category: {drink.strCategory}

-

Ingredients:

-
    - {[...Array(15).keys()].map((i) => { - const ingredient = drink[`strIngredient${i + 1}`]; - return ingredient &&
  • {ingredient}
  • ; - })} -
-

Instructions: {drink.strInstructions}

*/} + {drink.strIngredient1} +
+ handleIngredientToggle(drink.strIngredient1)} + /> + +
- ) - ) - : - "No Drinks availabale!!!" - + )) + : "No Ingredients Available!" }
- {/* Modal for My Ingredients */} + {/* Modal for selecting ingredients */} {isModalOpen && (

Select Your Ingredients

- {ingredients.map((ingredient) => ( -
+ {ingredients.map((drink) => ( +
handleIngredientToggle(ingredient)} + checked={selectedIngredients.includes(drink.strIngredient1)} + onChange={() => handleIngredientToggle(drink.strIngredient1)} /> - +
))}
Matches: {matches} cocktails
@@ -120,4 +97,4 @@ export const Custom = () => { )}
); -}; +}; \ No newline at end of file From 32f59938d117ea4c3894c857b8006c56ab9da596 Mon Sep 17 00:00:00 2001 From: Alex Ayala Palacin <95265085+aayalapalacin@users.noreply.github.com> Date: Thu, 1 May 2025 01:05:58 +0000 Subject: [PATCH 18/39] removed models not being used and added readme note for requests --- Pipfile | 1 + Pipfile.lock | 129 +++++++++++++++++- README.md | 2 + .../{0763d677d453_.py => 1b6206a1749f_.py} | 6 +- src/api/models.py | 20 +-- 5 files changed, 130 insertions(+), 28 deletions(-) rename migrations/versions/{0763d677d453_.py => 1b6206a1749f_.py} (89%) diff --git a/Pipfile b/Pipfile index 44e04f14ff..38a9224de2 100644 --- a/Pipfile +++ b/Pipfile @@ -20,6 +20,7 @@ typing-extensions = "*" flask-jwt-extended = "==4.6.0" wtforms = "==3.1.2" sqlalchemy = "*" +requests = "*" [requires] python_version = "3.13" diff --git a/Pipfile.lock b/Pipfile.lock index b201c3decc..ff61def229 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d2e672e650278aeeee2fe49bd76d76497d8b65a50f8b5dbb121d265cbc6ef4e5" + "sha256": "bf10612551b9927f533b121b086761c613604aa20a5af737364fcdf45ecb33e0" }, "pipfile-spec": 6, "requires": { @@ -34,11 +34,109 @@ }, "certifi": { "hashes": [ - "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", - "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe" + "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6", + "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3" ], "markers": "python_version >= '3.6'", - "version": "==2025.1.31" + "version": "==2025.4.26" + }, + "charset-normalizer": { + "hashes": [ + "sha256:0167ddc8ab6508fe81860a57dd472b2ef4060e8d378f0cc555707126830f2537", + "sha256:01732659ba9b5b873fc117534143e4feefecf3b2078b0a6a2e925271bb6f4cfa", + "sha256:01ad647cdd609225c5350561d084b42ddf732f4eeefe6e678765636791e78b9a", + "sha256:04432ad9479fa40ec0f387795ddad4437a2b50417c69fa275e212933519ff294", + "sha256:0907f11d019260cdc3f94fbdb23ff9125f6b5d1039b76003b5b0ac9d6a6c9d5b", + "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", + "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", + "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", + "sha256:0af291f4fe114be0280cdd29d533696a77b5b49cfde5467176ecab32353395c4", + "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", + "sha256:1a2bc9f351a75ef49d664206d51f8e5ede9da246602dc2d2726837620ea034b2", + "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", + "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", + "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", + "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", + "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", + "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", + "sha256:2a75d49014d118e4198bcee5ee0a6f25856b29b12dbf7cd012791f8a6cc5c496", + "sha256:2bdfe3ac2e1bbe5b59a1a63721eb3b95fc9b6817ae4a46debbb4e11f6232428d", + "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", + "sha256:2fb9bd477fdea8684f78791a6de97a953c51831ee2981f8e4f583ff3b9d9687e", + "sha256:311f30128d7d333eebd7896965bfcfbd0065f1716ec92bd5638d7748eb6f936a", + "sha256:329ce159e82018d646c7ac45b01a430369d526569ec08516081727a20e9e4af4", + "sha256:345b0426edd4e18138d6528aed636de7a9ed169b4aaf9d61a8c19e39d26838ca", + "sha256:363e2f92b0f0174b2f8238240a1a30142e3db7b957a5dd5689b0e75fb717cc78", + "sha256:3a3bd0dcd373514dcec91c411ddb9632c0d7d92aed7093b8c3bbb6d69ca74408", + "sha256:3bed14e9c89dcb10e8f3a29f9ccac4955aebe93c71ae803af79265c9ca5644c5", + "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", + "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", + "sha256:4532bff1b8421fd0a320463030c7520f56a79c9024a4e88f01c537316019005a", + "sha256:49402233c892a461407c512a19435d1ce275543138294f7ef013f0b63d5d3765", + "sha256:4c0907b1928a36d5a998d72d64d8eaa7244989f7aaaf947500d3a800c83a3fd6", + "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", + "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", + "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", + "sha256:619a609aa74ae43d90ed2e89bdd784765de0a25ca761b93e196d938b8fd1dbbd", + "sha256:6e27f48bcd0957c6d4cb9d6fa6b61d192d0b13d5ef563e5f2ae35feafc0d179c", + "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", + "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", + "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176", + "sha256:75832c08354f595c760a804588b9357d34ec00ba1c940c15e31e96d902093770", + "sha256:7709f51f5f7c853f0fb938bcd3bc59cdfdc5203635ffd18bf354f6967ea0f824", + "sha256:78baa6d91634dfb69ec52a463534bc0df05dbd546209b79a3880a34487f4b84f", + "sha256:7974a0b5ecd505609e3b19742b60cee7aa2aa2fb3151bc917e6e2646d7667dcf", + "sha256:7a4f97a081603d2050bfaffdefa5b02a9ec823f8348a572e39032caa8404a487", + "sha256:7b1bef6280950ee6c177b326508f86cad7ad4dff12454483b51d8b7d673a2c5d", + "sha256:7d053096f67cd1241601111b698f5cad775f97ab25d81567d3f59219b5f1adbd", + "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", + "sha256:807f52c1f798eef6cf26beb819eeb8819b1622ddfeef9d0977a8502d4db6d534", + "sha256:80ed5e856eb7f30115aaf94e4a08114ccc8813e6ed1b5efa74f9f82e8509858f", + "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", + "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", + "sha256:89149166622f4db9b4b6a449256291dc87a99ee53151c74cbd82a53c8c2f6ccd", + "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", + "sha256:8c60ca7339acd497a55b0ea5d506b2a2612afb2826560416f6894e8b5770d4a9", + "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de", + "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", + "sha256:97f68b8d6831127e4787ad15e6757232e14e12060bec17091b85eb1486b91d8d", + "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", + "sha256:9f0b8b1c6d84c8034a44893aba5e767bf9c7a211e313a9605d9c617d7083829f", + "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", + "sha256:ab36c8eb7e454e34e60eb55ca5d241a5d18b2c6244f6827a30e451c42410b5f7", + "sha256:b010a7a4fd316c3c484d482922d13044979e78d1861f0e0650423144c616a46a", + "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", + "sha256:b7b2d86dd06bfc2ade3312a83a5c364c7ec2e3498f8734282c6c3d4b07b346b8", + "sha256:b97e690a2118911e39b4042088092771b4ae3fc3aa86518f84b8cf6888dbdb41", + "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", + "sha256:c0429126cf75e16c4f0ad00ee0eae4242dc652290f940152ca8c75c3a4b6ee8f", + "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", + "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", + "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", + "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", + "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", + "sha256:d973f03c0cb71c5ed99037b870f2be986c3c05e63622c017ea9816881d2dd247", + "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", + "sha256:d9c3cdf5390dcd29aa8056d13e8e99526cda0305acc038b96b30352aff5ff2bb", + "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", + "sha256:dccbe65bd2f7f7ec22c4ff99ed56faa1e9f785482b9bbd7c717e26fd723a1d1e", + "sha256:dd78cfcda14a1ef52584dbb008f7ac81c1328c0f58184bf9a84c49c605002da6", + "sha256:e218488cd232553829be0664c2292d3af2eeeb94b32bea483cf79ac6a694e037", + "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", + "sha256:ea0d8d539afa5eb2728aa1932a988a9a7af94f18582ffae4bc10b3fbdad0626e", + "sha256:eab677309cdb30d047996b36d34caeda1dc91149e4fdca0b1a039b3f79d9a807", + "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", + "sha256:ecddf25bee22fe4fe3737a399d0d177d72bc22be6913acfab364b40bce1ba83c", + "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", + "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", + "sha256:f30bf9fd9be89ecb2360c7d94a711f00c09b976258846efe40db3d05828e8089", + "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", + "sha256:fc54db6c8593ef7d4b2a331b58653356cf04f67c960f584edb7c3d8c97e8f39e", + "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", + "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616" + ], + "markers": "python_version >= '3.7'", + "version": "==3.4.1" }, "click": { "hashes": [ @@ -199,6 +297,14 @@ "index": "pypi", "version": "==23.0.0" }, + "idna": { + "hashes": [ + "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", + "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" + ], + "markers": "python_version >= '3.6'", + "version": "==3.10" + }, "itsdangerous": { "hashes": [ "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", @@ -447,6 +553,15 @@ "markers": "python_version >= '3.8'", "version": "==6.0.2" }, + "requests": { + "hashes": [ + "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", + "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.32.3" + }, "six": { "hashes": [ "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", @@ -528,11 +643,11 @@ }, "urllib3": { "hashes": [ - "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", - "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d" + "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", + "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813" ], "markers": "python_version >= '3.9'", - "version": "==2.3.0" + "version": "==2.4.0" }, "werkzeug": { "hashes": [ diff --git a/README.md b/README.md index e69de29bb2..a3b1ebfded 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,2 @@ +If you receive error of requests module not found: +pipenv install requests diff --git a/migrations/versions/0763d677d453_.py b/migrations/versions/1b6206a1749f_.py similarity index 89% rename from migrations/versions/0763d677d453_.py rename to migrations/versions/1b6206a1749f_.py index 88964176f1..e1de76e9f0 100644 --- a/migrations/versions/0763d677d453_.py +++ b/migrations/versions/1b6206a1749f_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 0763d677d453 +Revision ID: 1b6206a1749f Revises: -Create Date: 2025-02-25 14:47:16.337069 +Create Date: 2025-05-01 01:02:33.208414 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '0763d677d453' +revision = '1b6206a1749f' down_revision = None branch_labels = None depends_on = None diff --git a/src/api/models.py b/src/api/models.py index ee1f4b54a8..19317dfbcd 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -10,29 +10,13 @@ class User(db.Model): password: Mapped[str] = mapped_column(nullable=False) is_active: Mapped[bool] = mapped_column(Boolean(), nullable=False) -class ingredients(db.Model): - id: Mapped[int] = mapped_column(primary_key=True) - name: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) - item: Mapped[str] = mapped_column(nullable=False) - item: Mapped[str] = mapped_column(nullable=False) - item: Mapped[str] = mapped_column(nullable=False) - item: Mapped[str] = mapped_column(nullable=False) -class Custom(db.Model): - id: Mapped[int] = mapped_column(primary_key=True) - email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) - password: Mapped[str] = mapped_column(nullable=False) - is_active: Mapped[bool] = mapped_column(Boolean(), nullable=False) - # "strIngredient1": "Tequila",whiskey,vodka,gin,bourben,brandy - # "strIngredient2": "Triple sec",agave,bitters,grand marnie, - # - # "strIngredient4": "Salt",sugar,,lemon,lime,pinapple, - def serialize(self): return { "id": self.id, "email": self.email, # do not serialize the password, its a security breach - } \ No newline at end of file + } + From dc35aa07cb0c482dac674c6c2e6692c9d75fcd30 Mon Sep 17 00:00:00 2001 From: Jackbringas <102544709+Jackbringas@users.noreply.github.com> Date: Thu, 1 May 2025 01:39:02 +0000 Subject: [PATCH 19/39] main page added as figma draft --- src/front/pages/mainpage.jsx | 65 ++++++++++++++++++++++++++++++++++++ src/front/routes.jsx | 2 ++ 2 files changed, 67 insertions(+) create mode 100644 src/front/pages/mainpage.jsx diff --git a/src/front/pages/mainpage.jsx b/src/front/pages/mainpage.jsx new file mode 100644 index 0000000000..05768fe50a --- /dev/null +++ b/src/front/pages/mainpage.jsx @@ -0,0 +1,65 @@ +import React from "react"; + +export const MainPage = () => { + return ( +
+ {/* Logo */} +
+

LOGO

+
+ + {/* Sign Up */} +
+ +
+ + {/* Sign In */} +
+ +
+ + {/* Password Recovery */} +
+ +
+
+ ); +}; diff --git a/src/front/routes.jsx b/src/front/routes.jsx index 8dbd91e3c3..028c1502bf 100644 --- a/src/front/routes.jsx +++ b/src/front/routes.jsx @@ -14,6 +14,7 @@ import { SignUp } from "./pages/signUp"; import { Search } from "./pages/Search"; import { SignIn } from "./pages/signIn"; +import { MainPage } from "./pages/mainpage"; export const router = createBrowserRouter( @@ -36,6 +37,7 @@ export const router = createBrowserRouter( } /> } /> + } /> ) From 9b91f7096f3b88b210e6e7cfe6668404c5ac19d9 Mon Sep 17 00:00:00 2001 From: XxDrancerxX Date: Thu, 1 May 2025 21:41:13 +0000 Subject: [PATCH 20/39] displaying selectedplace --- Pipfile | 3 - public/index.html | 36 +++++++++- src/api/routes.py | 4 ++ src/front/components/Navbar.jsx | 6 ++ src/front/index.css | 10 +-- src/front/pages/GoogleApi.jsx | 113 ++++++++++++++++++++++++++++++-- src/front/routes.jsx | 24 +------ 7 files changed, 159 insertions(+), 37 deletions(-) diff --git a/Pipfile b/Pipfile index a5ed4aa8c2..834a2dde60 100644 --- a/Pipfile +++ b/Pipfile @@ -19,11 +19,8 @@ flask-jwt-extended = "==4.6.0" wtforms = "==3.1.2" sqlalchemy = "*" requests = "*" -<<<<<<< HEAD flask-cors = "*" python-dotenv = "*" -======= ->>>>>>> cfd5594c59bfe7a03081100a746d782ca9bec721 [requires] python_version = "3.13" diff --git a/public/index.html b/public/index.html index 9462644fe9..d539df0090 100644 --- a/public/index.html +++ b/public/index.html @@ -1 +1,35 @@ -Hello Rigo with Vanilla.js
\ No newline at end of file + + + + + + Hello Rigo with Vanilla.js + + + + + + +
+ + + + diff --git a/src/api/routes.py b/src/api/routes.py index 98ab2d1e8e..df201af063 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -26,6 +26,7 @@ def get_places_of_drinks(): GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") # ==>> get the google api key from the environment variables data = request.get_json()# ==>>Looks at the body of the incoming HTTP request and it parses the JSON text and returns a Python dict (or list) representing that JSON. + if not isinstance(data, dict): # ==>> check if the data is a dictionary return jsonify({"error": "Payload must be a JSON object"}), 400 # ==>> if the data is not a dictionary, return a 400 error @@ -66,13 +67,16 @@ def get_places_of_drinks(): if opennow: # ==>> check if the opennow parameter is present in the request params["opennow"] = "true" # ==>> add the opennow parameter to the request + res = requests.get(url, params=params) # ==>> make a request to the google api with the parameters + if res.status_code != 200: # ==>> check if the request was successful return jsonify({ "error": "Failed to fetch data from Google", "details": res.text }), 500 body = res.json() + print("📄 [Flask] Google JSON:", body) places = body.get("results", []) next_page_token = body.get("next_page_token") diff --git a/src/front/components/Navbar.jsx b/src/front/components/Navbar.jsx index b9e5cce508..23a9c4d843 100644 --- a/src/front/components/Navbar.jsx +++ b/src/front/components/Navbar.jsx @@ -18,6 +18,12 @@ export const Navbar = () => {
+
+ + + +
+
); diff --git a/src/front/index.css b/src/front/index.css index 356d0152bf..64e88b53aa 100644 --- a/src/front/index.css +++ b/src/front/index.css @@ -32,17 +32,17 @@ .modal button:hover { background: #0056b3; } -body { +/* body { margin: 0; padding: 0; - background: linear-gradient(135deg, #a1c4fd, #c2e9fb); /* Gradient background */ + background: linear-gradient(135deg, #a1c4fd, #c2e9fb); Gradient background font-family: 'Arial', sans-serif; display: flex; justify-content: center; align-items: center; - min-height: 100vh; /* Full screen height */ - color: #333; /* Text color */ -} + min-height: 100vh; Full screen height + color: #333; Text color +} */ h1, h2, h3 { font-family: 'Helvetica', sans-serif; diff --git a/src/front/pages/GoogleApi.jsx b/src/front/pages/GoogleApi.jsx index 986861a5a7..f25d2ede8a 100644 --- a/src/front/pages/GoogleApi.jsx +++ b/src/front/pages/GoogleApi.jsx @@ -1,21 +1,120 @@ import { Link } from "react-router-dom"; import useGlobalReducer from "../hooks/useGlobalReducer"; - +import { useState } from "react"; export const GoogleApi = () => { - const { store, dispatch } = useGlobalReducer() + const { store, dispatch } = useGlobalReducer(); + const [places, setPlaces] = useState([]); + const [error, setError] = useState(""); + const [selectedPlace, setSelectedPlace] = useState(null); + + const handleSearch = () => { // Function to handle the search button click + if (!navigator.geolocation) { // Check if geolocation is supported on browser // Function to handle the search button click--object is provided by the browser's JavaScript engine + setError("Geolocation is not supported by this browser."); // If geolocation is not supported, set an error message + return; // Exit the function if geolocation is not supported + } + + navigator.geolocation.getCurrentPosition( // Get the current position of the user JavaScript feature for web development. + async (position) => { // Callback function to handle the position received from geolocation + const { latitude, longitude } = position.coords; // Extract latitude and longitude from the position object + const payload = { latitude, longitude, cocktail: "mojito" }; + console.log("▶️ Using coords:", latitude, longitude); + console.log("!!!!This is the object of the geolocation!!! :", position); + + + try { // Try to fetch places from the backend API using the coordinates and cocktail type + const res = await fetch(`${import.meta.env.VITE_BACKEND_URL}/api/places`, { + method: "POST", + headers: { "Content-Type": "application/json" }, // Fixed header key + body: JSON.stringify(payload), + }); + console.log("▶️ Fetch returned status:", res.status); + const data = await res.json(); + console.log("▶️ Data received:", data); + + if (data.error) { // Check if there is an error in the response + console.error("!!!Backend error:", data.error); + setError(data.error); // Set the error state with the error message from the backend + setPlaces([]); + } else { + setError(""); // Clear any previous error messages + console.log("▶️ Places found:", data.places); + setPlaces(data.places); + } + } catch (err) { // Catch any errors that occur during the fetch operation + console.error("!!!Error fetching places:", err); + setError(err.message); + } + }, + (geoErr) => { + console.error("!!!Geolocation error:", geoErr); + setError("Unable to retrieve your location. Please try again later."); + } + ); + }; return ( -
{/* Full height for the container */} -
{/* 70% height for the row */} -
aaa
-
aaa
+
+
+
+ +
+
+
+ + {error &&
{error}
} + {places.length > 0 ? ( +
    + {places.map((place, index) => ( +
  • +
    {place.name}
    +

    {place.address}

    +

    Rating: {place.rating}

    +

    Opinions: {place.user_ratings_total}

    + +
  • + ))} + +
+ ) : (

No places found.

)} + + +
+
-
{/* 30% height for the row */} +
bbb
+
); }; + + + + + + + + + + + +// {error &&
{error}
} {/* Display error message if exists */} +// {places.length > 0 ? ( +//
    +// {places.map((place, index) => ( +//
  • +//
    {place.name}
    +//

    {place.address}

    +//

    Rating: {place.rating}

    +// Go somewhere +//
  • +// ))} +//
+// ) : ( +//

No places found.

+// )} +// {/* Display the list of places if available */} diff --git a/src/front/routes.jsx b/src/front/routes.jsx index 0594b12b05..adeccbe29d 100644 --- a/src/front/routes.jsx +++ b/src/front/routes.jsx @@ -36,26 +36,8 @@ export const router = createBrowserRouter( } /> } /> } /> + } /> + } /> - ) -//======= - // Root Route: All navigation will start from here. - } errorElement={

Not found!

} > - - {/* Nested Routes: Defines sub-routes within the BaseHome component. */} - } /> - } /> {/* Dynamic route for single items */} - } /> - } /> - } /> - } /> -<<<<<<< HEAD - } /> -======= - } /> ->>>>>>> cfd5594c59bfe7a03081100a746d782ca9bec721 - - - ) -//>>>>>>> main +) ); \ No newline at end of file From bafc316a152f34275c9fb8b92f72bc05a1073bb0 Mon Sep 17 00:00:00 2001 From: Wdrew232 Date: Fri, 2 May 2025 23:20:00 +0000 Subject: [PATCH 21/39] fixed search code --- src/front/index.css | 131 ++++++++++++++++++++++++++++++++++++- src/front/pages/Search.jsx | 81 ++++++++++++++++++----- src/front/routes.jsx | 57 +++++----------- 3 files changed, 212 insertions(+), 57 deletions(-) diff --git a/src/front/index.css b/src/front/index.css index 356d0152bf..08d866d504 100644 --- a/src/front/index.css +++ b/src/front/index.css @@ -64,7 +64,7 @@ button:hover { background-color: #0056b3; /* Hover background */ transform: scale(1.05); /* Slight zoom on hover */ } -.Cocktail { +/* .Cocktail { display: flex; flex-wrap: wrap; gap: 20px; @@ -101,4 +101,133 @@ button:hover { .drink:hover { transform: scale(1.05); box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); +} */ +/* Container for the search functionality */ +.search-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 20px; + background: rgba(255, 255, 255, 0.8); + border-radius: 10px; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); + max-width: 600px; + margin: auto; } + +/* Search Bar */ +.search-bar { + display: flex; + width: 100%; + gap: 10px; +} + +/* Search Input */ +.search-input { + flex: 1; + padding: 10px; + border: 2px solid #007bff; + border-radius: 5px; + font-size: 1rem; + transition: border-color 0.3s, box-shadow 0.3s; +} + +.search-input:focus { + border-color: #0056b3; + box-shadow: 0px 0px 8px rgba(0, 123, 255, 0.5); + outline: none; +} + +/* Search Button */ +.search-button { + padding: 10px 15px; + background-color: #007bff; + color: white; + font-size: 1rem; + border: none; + border-radius: 5px; + cursor: pointer; + transition: background 0.3s, transform 0.2s; +} + +.search-button:hover { + background-color: #0056b3; + transform: scale(1.05); +} + +.search-button:active { + background-color: #004494; + transform: scale(0.95); +} + +/* Cocktail List */ +.cocktail-list { + display: flex; + flex-wrap: wrap; + gap: 20px; + justify-content: center; + margin-top: 20px; +} + +/* Individual Cocktail Cards */ +.cocktail-card { + width: 250px; + padding: 15px; + border: 1px solid #ddd; + border-radius: 8px; + box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); + background: white; + text-align: center; + transition: transform 0.2s; +} + +.cocktail-card:hover { + transform: scale(1.05); +} + +/* Cocktail Titles */ +.cocktail-title { + font-size: 1.5rem; + color: #333; + margin-bottom: 10px; +} + +/* Cocktail Images */ +.cocktail-image { + width: 100%; + border-radius: 8px; + margin-bottom: 10px; +} + +/* Glass and Category */ +.cocktail-glass, .cocktail-category { + font-size: 1rem; + color: #555; +} + +/* Ingredients List */ +.ingredient-list { + list-style: none; + padding: 0; + text-align: left; +} + +.ingredient-item { + font-size: 0.9rem; + color: #555; +} + +/* Instructions */ +.cocktail-instructions { + font-size: 1rem; + color: #444; +} + +/* No Results Message */ +.no-results { + font-size: 1.2rem; + color: red; + margin-top: 20px; +} + diff --git a/src/front/pages/Search.jsx b/src/front/pages/Search.jsx index fec7660007..ad94d619c6 100644 --- a/src/front/pages/Search.jsx +++ b/src/front/pages/Search.jsx @@ -1,26 +1,75 @@ import React, { useState } from "react"; -const handleSearch = (searchItem) => { - fetch("https://www.thecocktaildb.com/api/json/v1/1/search.php?s=" + searchItem, { - method: "GET", - mode: "cors" // fine to include (default is also "cors") - // no custom headers → no CORS pre-flight → no 403/404 - }) - .then(res => res.json()) +const handleSearch = (searchItem, setDrinks) => { + if (!searchItem.trim()) { + console.error("Search input is empty"); + return; + } + + fetch(`https://www.thecocktaildb.com/api/json/v1/1/search.php?s=${searchItem}`) + .then(res => { + if (!res.ok) throw new Error(`HTTP error! Status: ${res.status}`); + return res.json(); + }) .then(data => { - console.log(data); // data.drinks is the array of results + if (data.drinks) { + setDrinks(data.drinks); + } else { + setDrinks([]); + console.error("No drinks found for this search"); + } }) - .catch(err => console.error(err)); + .catch(err => console.error("Fetch Error:", err)); }; + export const Search = () => { - const [Search, setSearch] = useState("") + const [search, setSearch] = useState(""); + const [drinks, setDrinks] = useState([]); return ( -
-
- setSearch(e.target.value)} /> +
+
+ setSearch(e.target.value)} + value={search} + /> + +
+ + {/* Display the fetched drinks */} +
+ {drinks.length > 0 ? ( + drinks.map((drink) => ( +
+

{drink.strDrink}

+ {drink.strDrink} +

Glass: {drink.strGlass}

+

Category: {drink.strCategory}

+

Ingredients:

+
    + {[...Array(15).keys()].map((i) => { + const ingredient = drink[`strIngredient${i + 1}`]; + const measure = drink[`strMeasure${i + 1}`]; + return ( + ingredient && ( +
  • + {`${measure || ""} ${ingredient}`.trim()} +
  • + ) + ); + })} +
+

Instructions: {drink.strInstructions}

+
+ )) + ) : ( +

No drinks found

+ )}
-
- ) -} + ); +}; diff --git a/src/front/routes.jsx b/src/front/routes.jsx index 0f6d7661f5..7bf91319d6 100644 --- a/src/front/routes.jsx +++ b/src/front/routes.jsx @@ -1,5 +1,4 @@ -// Import necessary components and functions from react-router-dom. - +// Import necessary components and functions from react-router-dom import { createBrowserRouter, createRoutesFromElements, @@ -10,50 +9,28 @@ import { Home } from "./pages/Home"; import { Single } from "./pages/Single"; import { Demo } from "./pages/Demo"; import { SignUp } from "./pages/signUp"; - import { Search } from "./pages/Search"; import { Custom } from "./pages/Custom"; - import { SignIn } from "./pages/signIn"; -import { Password } from "./pages/password";import { MainPage } from "./pages/mainpage"; - +import { Password } from "./pages/password"; +import { MainPage } from "./pages/mainpage"; export const router = createBrowserRouter( createRoutesFromElements( - // CreateRoutesFromElements function allows you to build route elements declaratively. - // Create your routes here, if you want to keep the Navbar and Footer in all views, add your new routes inside the containing Route. - // Root, on the contrary, create a sister Route, if you have doubts, try it! - // Note: keep in mind that errorElement will be the default page when you don't get a route, customize that page to make your project more attractive. - // Note: The child paths of the Layout element replace the Outlet component with the elements contained in the "element" attribute of these child paths. + // Root Route: All navigation will start from Layout + } errorElement={

Not Found!

}> + + {/* Nested Routes */} + } /> {/* Default homepage */} + } /> {/* Dynamic route */} + } /> + } /> + } /> + } /> + } /> + } /> + } /> -//<<<<<<< 53-add-pictures-to-all-of-the-ingredients - // Root Route: All navigation will start from here. - } errorElement={

Not found!

} > - - {/* Nested Routes: Defines sub-routes within the BaseHome component. */} - } /> - } /> {/* Dynamic route for single items */} - } /> - } /> - } /> - } /> ) -//======= - // Root Route: All navigation will start from here. - } errorElement={

Not found!

} > - - {/* Nested Routes: Defines sub-routes within the BaseHome component. */} - } /> - } /> {/* Dynamic route for single items */} - } /> - } /> - } /> - } /> - } /> - } /> - - - ) -//>>>>>>> main -); \ No newline at end of file +); From f9f0ff27764872fea5ca430c337bce255169d74b Mon Sep 17 00:00:00 2001 From: Jackbringas <102544709+Jackbringas@users.noreply.github.com> Date: Sat, 3 May 2025 00:36:21 +0000 Subject: [PATCH 22/39] Sign Up working, missing fetch --- src/front/pages/signUp.jsx | 48 +++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/src/front/pages/signUp.jsx b/src/front/pages/signUp.jsx index 7f136caf86..845c890532 100644 --- a/src/front/pages/signUp.jsx +++ b/src/front/pages/signUp.jsx @@ -1,12 +1,33 @@ -import React from "react"; +import React, { useState } from "react"; export const SignUp = () => { - return (
{ + setFormData ({...formData, [e.target.name]: e.target.value}); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + //need to update with fetch when be ready// + console.log ("Form sent:",formData); + alert("Sign Up info ready to send"); + }; + + return ( +
+
+ style={{ minHeight: "100vh" }}> -
+
{
@@ -39,6 +63,9 @@ export const SignUp = () => {
@@ -47,6 +74,9 @@ export const SignUp = () => {
@@ -55,14 +85,20 @@ export const SignUp = () => {
+ - + +
+ ); }; From 40f828f0310bece7585c0a45b9a531ed2987cadb Mon Sep 17 00:00:00 2001 From: Wdrew232 Date: Sun, 4 May 2025 16:01:00 +0000 Subject: [PATCH 23/39] added custom css --- src/front/index.css | 68 ++++++++++++++++++++++++++++---------- src/front/pages/Custom.jsx | 45 +++++++++---------------- 2 files changed, 65 insertions(+), 48 deletions(-) diff --git a/src/front/index.css b/src/front/index.css index 92012774b7..662babba77 100644 --- a/src/front/index.css +++ b/src/front/index.css @@ -1,34 +1,66 @@ -.logo { - margin-top: 4rem; - margin-left: 20px; +/* ✅ Custom Component Styles */ +.custom-app { + font-family: Arial, sans-serif; + padding: 20px; + text-align: center; +} + +.custom-cocktail-list { + display: flex; + flex-wrap: wrap; + gap: 20px; + justify-content: center; +} + +.custom-drink-card { + background-color: #f9f9f9; + border: 1px solid #ddd; + padding: 15px; + border-radius: 5px; + width: 250px; +} + +.custom-drink-card h2 { + font-size: 20px; + margin-bottom: 10px; } -.modal { +.custom-modal { position: fixed; top: 50%; left: 50%; - transform: translate(-50%, -50%); - background: white; + width: 300px; padding: 20px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); - border-radius: 8px; - z-index: 1000; + background: white; + border-radius: 5px; + transform: translate(-50%, -50%); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +.custom-ingredient { + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 5px; } -.modal h2 { - margin-bottom: 20px; +.custom-matches { + margin-top: 10px; + font-weight: bold; } -.modal button { - margin: 10px; - padding: 10px 15px; +.custom-save-btn, +.custom-close-btn { + margin-top: 10px; + padding: 10px; border: none; - background: #007bff; + background-color: #007bff; color: white; cursor: pointer; - border-radius: 5px; + border-radius: 3px; } -.modal button:hover { - background: #0056b3; +.custom-save-btn:hover, +.custom-close-btn:hover { + background-color: #0056b3; } diff --git a/src/front/pages/Custom.jsx b/src/front/pages/Custom.jsx index 918aa6b417..1c47409b5f 100644 --- a/src/front/pages/Custom.jsx +++ b/src/front/pages/Custom.jsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from "react"; +import "./Custom.css"; // ✅ Import unique CSS const handleFetch = (setDrinks) => { fetch("https://www.thecocktaildb.com/api/json/v1/1/list.php?i=list") @@ -21,9 +22,7 @@ export const Custom = () => { const [isModalOpen, setIsModalOpen] = useState(false); useEffect(() => { - // Fetch cocktails on app load handleFetch(setDrinks); - // Extract ingredients from fetched data const extractIngredients = (cocktails) => { const allIngredients = cocktails.reduce((acc, drink) => { [...Array(15).keys()].forEach((i) => { @@ -48,7 +47,6 @@ export const Custom = () => { setSelectedIngredients(newSelected); - // Calculate matches const matchCount = drinks.filter((drink) => newSelected.every((ing) => { return [...Array(15).keys()] @@ -68,43 +66,30 @@ export const Custom = () => { localStorage.setItem("customSets", JSON.stringify([...savedSets, customSet])); setIsModalOpen(false); }; - console.log("drinks!!!", drinks) + return ( -
+
{/* Cocktail list */} -
- {drinks.length > 0 && drinks && drinks !== "no data found" ? +
+ {drinks.length > 0 && drinks && drinks !== "no data found" ? ( drinks.map((drink) => ( -
+

{drink.strIngredient1}

- {/* {drink.strDrink} -

Glass: {drink.strGlass}

-

Category: {drink.strCategory}

-

Ingredients:

-
    - {[...Array(15).keys()].map((i) => { - const ingredient = drink[`strIngredient${i + 1}`]; - return ingredient &&
  • {ingredient}
  • ; - })} -
-

Instructions: {drink.strInstructions}

*/}
- ) - ) - : - "No Drinks availabale!!!" - - } + )) + ) : ( + "No Drinks available!!!" + )}
{/* Modal for My Ingredients */} {isModalOpen && ( -
+

Select Your Ingredients

{ingredients.map((ingredient) => ( -
+
{
))} -
Matches: {matches} cocktails
- - +
Matches: {matches} cocktails
+ +
)}
From 01a2c03965659ed9c77c62e3c457f98d2eb23ff9 Mon Sep 17 00:00:00 2001 From: Jackbringas <102544709+Jackbringas@users.noreply.github.com> Date: Mon, 5 May 2025 03:05:11 +0000 Subject: [PATCH 24/39] signin funcionality done, missing fetch --- src/front/pages/signIn.jsx | 54 ++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/src/front/pages/signIn.jsx b/src/front/pages/signIn.jsx index 3913483d21..1f62666bb8 100644 --- a/src/front/pages/signIn.jsx +++ b/src/front/pages/signIn.jsx @@ -1,7 +1,37 @@ -import React from "react"; +import React, {useState} from "react"; export const SignIn = () => { - return (
{ + setFormData({...formData, [e.target.name]: e.target.value}); + setError(""); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + if(!formData.email || !formData.password){ + setError ("Please complete all fields"); + return; + } + + console.log("login sent:", formData); + alert("Login info ready to send"); + + //this is the place for the fetch + // const response = await fetch("BACKEND_URL/login", { ... }) + }; + + + return ( +
@@ -26,10 +56,19 @@ import React from "react"; }} >

Sign In

+ + {error &&( +
+ {error} +
+ )}
@@ -39,6 +78,9 @@ import React from "react";
@@ -46,8 +88,10 @@ import React from "react";
- - -
+ + Forgot Password? + + +
); }; \ No newline at end of file From 7d0cb8933d248cebdcc53231714b279aea907c1e Mon Sep 17 00:00:00 2001 From: XxDrancerxX Date: Mon, 5 May 2025 18:06:45 +0000 Subject: [PATCH 25/39] a --- package-lock.json | 34 ++++++-- package.json | 1 + src/api/routes.py | 16 +++- src/front/index.css | 17 ++++ src/front/pages/GoogleApi.jsx | 151 ++++++++++++++++++++++++++++------ 5 files changed, 186 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index c6d830fbeb..b1469bd2fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.1", "license": "ISC", "dependencies": { + "bootstrap-icons": "^1.12.1", "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -1594,6 +1595,22 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bootstrap-icons": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.12.1.tgz", + "integrity": "sha512-ekwupjsteHQmgGV+haQ0nNMoSyKCbJj5ou+06vFzb9uR2/bwN9isNEgXBaQzcT+fLzhKS3OaBNpwz8XdZlIgYQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -4675,9 +4692,9 @@ } }, "node_modules/vite": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.3.tgz", - "integrity": "sha512-5nXH+QsELbFKhsEfWLkHrvgRpTdGJzqOZ+utSdmPTvwHmvU6ITTm3xx+mRusihkcI8GeC7lCDyn3kDtiki9scw==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5810,6 +5827,11 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "bootstrap-icons": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.12.1.tgz", + "integrity": "sha512-ekwupjsteHQmgGV+haQ0nNMoSyKCbJj5ou+06vFzb9uR2/bwN9isNEgXBaQzcT+fLzhKS3OaBNpwz8XdZlIgYQ==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -7851,9 +7873,9 @@ } }, "vite": { - "version": "6.3.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.3.tgz", - "integrity": "sha512-5nXH+QsELbFKhsEfWLkHrvgRpTdGJzqOZ+utSdmPTvwHmvU6ITTm3xx+mRusihkcI8GeC7lCDyn3kDtiki9scw==", + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", "dev": true, "requires": { "esbuild": "^0.25.0", diff --git a/package.json b/package.json index 0eb5d90213..5eb61e2710 100755 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ ] }, "dependencies": { + "bootstrap-icons": "^1.12.1", "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/api/routes.py b/src/api/routes.py index df201af063..6413f08368 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -239,7 +239,7 @@ def get_place_details(): url = "https://maps.googleapis.com/maps/api/place/details/json" # ==>> url for the google api params = { "place_id": place_id, - "fields":"name,formatted_phone_number,opening_hours,website,reviews", + "fields":"name,formatted_phone_number,opening_hours,website,reviews,photos,formatted_address", # ==>> fields to be returned in the response "key": GOOGLE_API_KEY } res = requests.get(url, params=params) # ==>> make a request to the google api with the parameters @@ -251,13 +251,25 @@ def get_place_details(): if not details: # ==>> check if the details are present in the response return jsonify({"error": "No details found"}), 404 # ==>> return a 404 error if the details are missing + photo_urls = [] # ==>> initialize an empty list for the photo urls + for i in details.get("photos", []): # ==>> iterate over the photos in the details + ref = i.get("photo_reference") # ==>> get the photo reference from the photo + photo_urls.append( + f"https://maps.googleapis.com/maps/api/place/photo" + f"?maxwidth=400" + f"&photoreference={ref}" + f"&key={GOOGLE_API_KEY}" + ) + return jsonify({ "name": details.get("name"), "formatted_address": details.get("formatted_address"), "formatted_phone_number": details.get("formatted_phone_number", "N/A"), "opening_hours": details.get("opening_hours", {}), "website": details.get("website"), - "reviews": details.get("reviews", []) + "reviews": details.get("reviews", []), + "photos": photo_urls, # ==>> return the photo urls as a list }), 200 + diff --git a/src/front/index.css b/src/front/index.css index 64e88b53aa..c0a9de211d 100644 --- a/src/front/index.css +++ b/src/front/index.css @@ -102,3 +102,20 @@ button:hover { transform: scale(1.05); box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); } + +.card-text { + display: -webkit-box; /* Necesario para que funcione -webkit-line-clamp */ + + -webkit-box-orient: vertical; /* Orientación vertical */ + overflow: hidden; /* Ocultar el contenido que excede */ + text-overflow: ellipsis; /* Mostrar "..." al truncar */ +} + + +.clamp-3 { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 3; + overflow: hidden; + position: relative; /* so your "…see more" button can be positioned */ +} diff --git a/src/front/pages/GoogleApi.jsx b/src/front/pages/GoogleApi.jsx index f25d2ede8a..a1433798a9 100644 --- a/src/front/pages/GoogleApi.jsx +++ b/src/front/pages/GoogleApi.jsx @@ -1,13 +1,19 @@ import { Link } from "react-router-dom"; import useGlobalReducer from "../hooks/useGlobalReducer"; import { useState } from "react"; +import 'bootstrap-icons/font/bootstrap-icons.css'; + export const GoogleApi = () => { - const { store, dispatch } = useGlobalReducer(); const [places, setPlaces] = useState([]); const [error, setError] = useState(""); const [selectedPlace, setSelectedPlace] = useState(null); + const [reviews, setReviews] = useState([]) // State to hold reviews of the selected place + const [selectedReview, setSelectedReview] = useState(null); + const handleSearch = () => { // Function to handle the search button click + setSelectedPlace(null); // clear out old details + setError(""); // clear any past error if (!navigator.geolocation) { // Check if geolocation is supported on browser // Function to handle the search button click--object is provided by the browser's JavaScript engine setError("Geolocation is not supported by this browser."); // If geolocation is not supported, set an error message return; // Exit the function if geolocation is not supported @@ -52,15 +58,71 @@ export const GoogleApi = () => { ); }; + const handleSelect = async (placeId) => { // Function to handle the selection of a place + const payload = { place_id: placeId }; // Create a payload with the selected place ID + const options = { + method: "POST", + headers: { "Content-Type": "application/json" }, // Fixed header key + body: JSON.stringify(payload), // Send the selected place ID to the backend + } + try { + const res = await fetch(`${import.meta.env.VITE_BACKEND_URL}/api/places/details`, options); // Fetch details of the selected place from the backend API + console.log("==>> DATA SENT:", res); + if (!res.ok) { + console.log("!!!Error fetching place details:", res.statusText); + setError("Failed to fetch place details. Please try again later."); + return; // Exit the function if the response is not OK so that you don’t try to parse an empty or error body. + } + const details = await res.json(); // Parse the response JSON to get the place details + setSelectedPlace(details); // Set the selected place details in the state// //update state so React re-renders your detail pane + setSelectedReview(null); // Clear any previously selected review + setReviews([]); // Clear the reviews state when a new place is selected + console.log("▶️ Place details:", details); // Log the place details to the console + } + catch (err) { + console.log("!!!Error fetching place details:", err); + setError(err.message); // Set the error state with the error message + } + }; + + const showsReviews = (reviews) => { // Function to show reviews of the selected place + if (!reviews || reviews.length === 0) { // Check if reviews are undefined or empty + console.log("!!!No reviews found for this place or undefined reviews:", reviews); + setReviews([]); // Clear the reviews state if no reviews are found + return; // Exit the function if no reviews are available //stops the function if something's wrong → no unnecessary code runs after it. + } + setReviews(reviews); // Set the reviews state with the reviews of the selected place + } + + + return (
-
- +
+ {selectedPlace ?
+ {selectedPlace.photos[0] && ( + {selectedPlace.name} + )} +
+
{selectedPlace.name}
+

Go to the website

+
showsReviews(selectedPlace.reviews)}> + Reviews: + +
+
+
+ : +

+ Click a place to see more details +

}
- {error &&
{error}
} {places.length > 0 ? (
    @@ -68,9 +130,10 @@ export const GoogleApi = () => {
  • {place.name}

    {place.address}

    -

    Rating: {place.rating}

    -

    Opinions: {place.user_ratings_total}

    - +

    Rating: {place.rating}

    +

    Reviews: {place.user_ratings_total}

    + +
  • ))} @@ -81,9 +144,64 @@ export const GoogleApi = () => {
+
-
bbb
+
+
+ {reviews.map((r, index) => ( +
+
+ Rating: {r.rating} +
+
+
+
{r.author_name}:
+

+ {r.text} +

+ {r.text && r.text.length > 150 && ( + setSelectedReview(r)}> + See full review + + )} +
+ + {r.relative_time_description || "No date available"} + +
+
+
+ ))} + + + + + +
+ +
+ @@ -101,20 +219,3 @@ export const GoogleApi = () => { - -// {error &&
{error}
} {/* Display error message if exists */} -// {places.length > 0 ? ( -//
    -// {places.map((place, index) => ( -//
  • -//
    {place.name}
    -//

    {place.address}

    -//

    Rating: {place.rating}

    -// Go somewhere -//
  • -// ))} -//
-// ) : ( -//

No places found.

-// )} -// {/* Display the list of places if available */} From 2155cb233c4cbddef1d738354ee9c48d5ac833d1 Mon Sep 17 00:00:00 2001 From: XxDrancerxX Date: Mon, 5 May 2025 21:48:32 +0000 Subject: [PATCH 26/39] a --- src/front/pages/GoogleApi.jsx | 97 ++++++++++++++++++++++++++--------- 1 file changed, 73 insertions(+), 24 deletions(-) diff --git a/src/front/pages/GoogleApi.jsx b/src/front/pages/GoogleApi.jsx index a1433798a9..8fafbca909 100644 --- a/src/front/pages/GoogleApi.jsx +++ b/src/front/pages/GoogleApi.jsx @@ -9,6 +9,7 @@ export const GoogleApi = () => { const [selectedPlace, setSelectedPlace] = useState(null); const [reviews, setReviews] = useState([]) // State to hold reviews of the selected place const [selectedReview, setSelectedReview] = useState(null); + const [showPhotos, setShowPhotos] = useState(false); const handleSearch = () => { // Function to handle the search button click @@ -94,34 +95,85 @@ export const GoogleApi = () => { setReviews(reviews); // Set the reviews state with the reviews of the selected place } + const showMorePhotos = () => { + setShowPhotos(!showPhotos); // Toggle the showPhotos state to show or hide photos + } + return (
-
- {selectedPlace ?
- {selectedPlace.photos[0] && ( - {selectedPlace.name} - )} -
-
{selectedPlace.name}
-

Go to the website

-
showsReviews(selectedPlace.reviews)}> - Reviews: + {/* LEFT: Place details */} +
+ {selectedPlace ? ( +
+ {selectedPlace.photos[0] && ( + {selectedPlace.name} + )} +
+
{selectedPlace.name}
+

+ {selectedPlace.opening_hours?.open_now ? "Open" : "Closed"} +

+

{selectedPlace.formatted_address}

+

{selectedPlace.formatted_phone_number}

+

Go to the website

+ {selectedPlace.opening_hours?.weekday_text && ( +
+ Opening hours: +
    + {selectedPlace.opening_hours.weekday_text.map((day, idx) => ( +
  • {day}
  • + ))} +
+
+ )} + { e.preventDefault(); showsReviews(selectedPlace.reviews); }}> + See Reviews + +
+ + {showPhotos ? "Hide photos" : "See more photos"} +
-
- : -

- Click a place to see more details -

} + ) :

Click a place to see more details

}
-
+ + {/* CENTER: Photos */} +
+ {showPhotos && selectedPlace.photos && selectedPlace.photos.slice(1).map((photoUrl, idx) => ( + {`Photo + ))} + +
+ + {/* RIGHT: List of places */} +
{error &&
{error}
} {places.length > 0 ? ( @@ -132,18 +184,15 @@ export const GoogleApi = () => {

{place.address}

Rating: {place.rating}

Reviews: {place.user_ratings_total}

- ))} - - ) : (

No places found.

)} - - + ) :

No places found.

}
+
From c870f204a4782f85479c438bf1e8438987e9f22f Mon Sep 17 00:00:00 2001 From: XxDrancerxX Date: Mon, 5 May 2025 22:44:12 +0000 Subject: [PATCH 27/39] front-end-google-api --- src/front/pages/GoogleApi.jsx | 47 ++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/src/front/pages/GoogleApi.jsx b/src/front/pages/GoogleApi.jsx index 8fafbca909..82511a8a5c 100644 --- a/src/front/pages/GoogleApi.jsx +++ b/src/front/pages/GoogleApi.jsx @@ -10,6 +10,9 @@ export const GoogleApi = () => { const [reviews, setReviews] = useState([]) // State to hold reviews of the selected place const [selectedReview, setSelectedReview] = useState(null); const [showPhotos, setShowPhotos] = useState(false); + const [showReviewModal, setShowReviewModal] = useState(false); + const [modalReviewText, setModalReviewText] = useState(""); + const handleSearch = () => { // Function to handle the search button click @@ -122,7 +125,8 @@ export const GoogleApi = () => { }} /> )} -
+
{selectedPlace.name}

{selectedPlace.opening_hours?.open_now ? "Open" : "Closed"} @@ -154,6 +158,11 @@ export const GoogleApi = () => { {/* CENTER: Photos */}

+ {(!showPhotos || !selectedPlace || !selectedPlace.photos || selectedPlace.photos.length <= 1) && ( +
+ Click "See more photos" to view the gallery. +
+ )} {showPhotos && selectedPlace.photos && selectedPlace.photos.slice(1).map((photoUrl, idx) => ( { justifyContent: "space-evenly" // NEW LINE instead of margin-right on cards }} > + {reviews.length === 0 && ( +
+ Please click on See Reviews above to see all the reviews. +
+ )} + {reviews.map((r, index) => (
{
{r.author_name}:

{ {r.text}

{r.text && r.text.length > 150 && ( - setSelectedReview(r)}> + { + e.preventDefault(); + setModalReviewText(r.text); + setShowReviewModal(true); + }}> See full review )} + +
{r.relative_time_description || "No date available"} @@ -254,6 +275,24 @@ export const GoogleApi = () => { + {showReviewModal && ( +
setShowReviewModal(false)}> +
e.stopPropagation()}> +
+
+
Full Review
+ +
+
+

{modalReviewText}

+
+
+
+
+ )} +
); From 8dc2b34835e4abec11302f840280f129f9c7e8a8 Mon Sep 17 00:00:00 2001 From: Jackbringas <102544709+Jackbringas@users.noreply.github.com> Date: Mon, 5 May 2025 23:38:30 +0000 Subject: [PATCH 28/39] test --- Pipfile | 1 + Pipfile.lock | 3 ++- src/api/routes.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 38a9224de2..30469c3e46 100644 --- a/Pipfile +++ b/Pipfile @@ -21,6 +21,7 @@ flask-jwt-extended = "==4.6.0" wtforms = "==3.1.2" sqlalchemy = "*" requests = "*" +werkzeug = "*" [requires] python_version = "3.13" diff --git a/Pipfile.lock b/Pipfile.lock index ff61def229..145eef5b71 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "bf10612551b9927f533b121b086761c613604aa20a5af737364fcdf45ecb33e0" + "sha256": "e942ca1d51677e09f303df46ce3e18118677e46b7bd0fd08ec9f9ccb795631cb" }, "pipfile-spec": 6, "requires": { @@ -654,6 +654,7 @@ "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746" ], + "index": "pypi", "markers": "python_version >= '3.9'", "version": "==3.1.3" }, diff --git a/src/api/routes.py b/src/api/routes.py index 52675243a4..9e9bd9434c 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -9,6 +9,8 @@ from flask_cors import CORS # ==>> loads the environment variables from the .env file, pip install python-dotenv from dotenv import load_dotenv +from werkzeug.security import generate_password_hash + load_dotenv() # reads .env and sets those variables into your environment @@ -254,4 +256,31 @@ def get_place_details(): "reviews": details.get("reviews", []) }), 200 +#Jackie +@api.route("/signup", methods=["POST"]) +def signup(): + name = request.json.get ("name", None) + email = request.json.get("email", None) + password = request.json.get("password", None) + + if not name or not email or not password: + return jsonify({"msg": "Name, email and password are required"}), 400 + + user = User.query.filter_by(email=email).first() + if user: + return jsonify({"msg": "User already exists"}),409 + + hashed_password = generate_password_hash(password) + + new_user = User( + name=name, + email=email, + password= hashed_password, + is_active=True + ) + + db.session.add(new_user) + db.session.commit() + + return jsonify ("User created sucessfully"),201 From b55b78032b478d01d6b78ecfbb3d0693d4348e0c Mon Sep 17 00:00:00 2001 From: XxDrancerxX Date: Tue, 6 May 2025 01:06:18 +0000 Subject: [PATCH 29/39] sig-in-started --- src/api/models.py | 1 + src/api/routes.py | 404 +++++++++++++++++++++++++------------------ src/front/routes.jsx | 1 + 3 files changed, 235 insertions(+), 171 deletions(-) diff --git a/src/api/models.py b/src/api/models.py index 19317dfbcd..da250f8c35 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -6,6 +6,7 @@ class User(db.Model): id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str]= mapped_column(String(120), nullable=False) email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) password: Mapped[str] = mapped_column(nullable=False) is_active: Mapped[bool] = mapped_column(Boolean(), nullable=False) diff --git a/src/api/routes.py b/src/api/routes.py index 6413f08368..007729c1b8 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -9,8 +9,14 @@ from flask_cors import CORS # ==>> loads the environment variables from the .env file, pip install python-dotenv from dotenv import load_dotenv +from werkzeug.security import check_password_hash, generate_password_hash +# ==>> this is used to create a JWT token for the user, pip install flask-jwt-extended +from flask_jwt_extended import create_access_token load_dotenv() # reads .env and sets those variables into your environment +# db and User → for querying the database. +# check_password_hash → for checking if the password is correct. +# create_access_token → for making a token. # ==>>a collection of routes, error handlers, etc., that you group together in one file (here, api/routes.py). api = Blueprint('api', __name__) @@ -23,18 +29,25 @@ # ==>> this is the endpoint that will be called from the front end # ==>> Search Places by coordinates: Accepts E.G: { latitude: 40.75, longitude: -73.99, cocktail: "Mojito" } @api.route('/places', methods=['POST']) -def get_places_of_drinks(): - GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") # ==>> get the google api key from the environment variables - data = request.get_json()# ==>>Looks at the body of the incoming HTTP request and it parses the JSON text and returns a Python dict (or list) representing that JSON. - - if not isinstance(data, dict): # ==>> check if the data is a dictionary - return jsonify({"error": "Payload must be a JSON object"}), 400 # ==>> if the data is not a dictionary, return a 400 error - - if not GOOGLE_API_KEY: - return jsonify({"error": "Google API key not found"}), 500 # ==>> if the key is not found, return a 500 error - - page_token = data.get("next_page_token") # ==>> get the page token from the request that is used for pagination. - url = "https://maps.googleapis.com/maps/api/place/nearbysearch/json" # ==>> url for the google api +def get_places_of_drinks(): + + # ==>> get the google api key from the environment variables + GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") + # ==>>Looks at the body of the incoming HTTP request and it parses the JSON text and returns a Python dict (or list) representing that JSON. + data = request.get_json() + + if not isinstance(data, dict): # ==>> check if the data is a dictionary + # ==>> if the data is not a dictionary, return a 400 error + return jsonify({"error": "Payload must be a JSON object"}), 400 + + if not GOOGLE_API_KEY: + # ==>> if the key is not found, return a 500 error + return jsonify({"error": "Google API key not found"}), 500 + + # ==>> get the page token from the request that is used for pagination. + page_token = data.get("next_page_token") + # ==>> url for the google api + url = "https://maps.googleapis.com/maps/api/place/nearbysearch/json" if page_token: # ==>> check if the page token is present in the request # We’re loading a “next” page params = { @@ -42,51 +55,54 @@ def get_places_of_drinks(): "key": GOOGLE_API_KEY # ==>> get the google api key from the environment variables } else: - opennow = data.get("opennow") - latitude = data.get("latitude") - longitude = data.get("longitude") - cocktail = data.get("cocktail") - - - if not all([latitude, longitude, cocktail ]): # ==>> check if all the values are present in the request - return jsonify({"error": "Missing data"}), 400 # ==>> return a 400 error if any of the values are missing - try: - latitude = float(latitude) - longitude = float(longitude) - - except (ValueError, TypeError): # ==>> check if the values are numbers - return jsonify({"error": "Latitude and longitude must be numbers"}), 400 # ==>> if not, return a 400 error - # First page: use location, radius, keyword… - params = { # ==>> parameters for the google api - "location": f"{latitude},{longitude}", # ==>> location of the user - "radius": 5000, - "type": "bar", - "keyword": cocktail, - "key": GOOGLE_API_KEY - } - if opennow: # ==>> check if the opennow parameter is present in the request - params["opennow"] = "true" # ==>> add the opennow parameter to the request - - - res = requests.get(url, params=params) # ==>> make a request to the google api with the parameters - + opennow = data.get("opennow") + latitude = data.get("latitude") + longitude = data.get("longitude") + cocktail = data.get("cocktail") + + # ==>> check if all the values are present in the request + if not all([latitude, longitude, cocktail]): + # ==>> return a 400 error if any of the values are missing + return jsonify({"error": "Missing data"}), 400 + try: + latitude = float(latitude) + longitude = float(longitude) + + except (ValueError, TypeError): # ==>> check if the values are numbers + # ==>> if not, return a 400 error + return jsonify({"error": "Latitude and longitude must be numbers"}), 400 + # First page: use location, radius, keyword… + params = { # ==>> parameters for the google api + "location": f"{latitude},{longitude}", # ==>> location of the user + "radius": 5000, + "type": "bar", + "keyword": cocktail, + "key": GOOGLE_API_KEY + } + if opennow: # ==>> check if the opennow parameter is present in the request + # ==>> add the opennow parameter to the request + params["opennow"] = "true" + + # ==>> make a request to the google api with the parameters + res = requests.get(url, params=params) + if res.status_code != 200: # ==>> check if the request was successful return jsonify({ "error": "Failed to fetch data from Google", "details": res.text - }), 500 + }), 500 body = res.json() print("📄 [Flask] Google JSON:", body) places = body.get("results", []) next_page_token = body.get("next_page_token") - filtered_places = [] for place in places: if place.get("business_status") != "OPERATIONAL": - continue - ref = place.get("photos", [{}])[0].get("photo_reference") # ==>> ↓↓↓get the photo reference of the place.↓↓↓ This accesses the "photos" key in the place dictionary, which is a list of dictionaries. ↓↓↓It tries to get the first dictionary in that list (or an empty dictionary if the list is empty) and then accesses the "photo_reference" key. - # ==>> check if the photo reference is not empty ## ↑↑↑one representative image per place,↑↑↑ and the first one is usually the best (it’s what Google thinks is most relevant)↑↑↑ + continue + # ==>> ↓↓↓get the photo reference of the place.↓↓↓ This accesses the "photos" key in the place dictionary, which is a list of dictionaries. ↓↓↓It tries to get the first dictionary in that list (or an empty dictionary if the list is empty) and then accesses the "photo_reference" key. + ref = place.get("photos", [{}])[0].get("photo_reference") + # ==>> check if the photo reference is not empty ## ↑↑↑one representative image per place,↑↑↑ and the first one is usually the best (it’s what Google thinks is most relevant)↑↑↑ if ref: photo_url = ( f"https://maps.googleapis.com/maps/api/place/photo" # ==>> url for the google api @@ -98,22 +114,22 @@ def get_places_of_drinks(): photo_url = None filtered_places.append({ - "name": place.get("name"), - "address": place.get("vicinity") or place.get("formatted_address"), # ==>> get the address of the place, First, it tries to get the "vicinity" key (a nearby address). If "vicinity" is not available, it falls back to "formatted_address" (the full address). - "rating": place.get("rating"), - "location": place["geometry"]["location"], # ==>> get the location of the place This accesses the "geometry" key in the place dictionary, which contains a "location" key. "location" is another dictionary with latitude and longitude values. - "user_ratings_total": place.get("user_ratings_total"), + "name": place.get("name"), + # ==>> get the address of the place, First, it tries to get the "vicinity" key (a nearby address). If "vicinity" is not available, it falls back to "formatted_address" (the full address). + "address": place.get("vicinity") or place.get("formatted_address"), + "rating": place.get("rating"), + # ==>> get the location of the place This accesses the "geometry" key in the place dictionary, which contains a "location" key. "location" is another dictionary with latitude and longitude values. + "location": place["geometry"]["location"], + "user_ratings_total": place.get("user_ratings_total"), "place_id": place.get("place_id"), "photo_url": photo_url - }) + }) return jsonify({ - "places": filtered_places, - "next_page_token": next_page_token + "places": filtered_places, + "next_page_token": next_page_token }), 200 # ==>> return the filtered places as a json object with a 200 status code - - # ==>> Search Places in a specific location:Accepts E.G: { zip_code: "10001" } Returns { latitude: 40.75, longitude: -73.99} @api.route('/places/by-location', methods=['POST']) @@ -125,151 +141,197 @@ def get_places_by_location(): return jsonify({"error": "Data must be a JSON object"}), 400 if not GOOGLE_API_KEY: return jsonify({"error": "Google API key not found"}), 500 - + page_token = data.get("next_page_token") filtered_places = [] - + if page_token: # ==>> check if the page token is present in the request # We’re loading a “next” page - places_url = "https://maps.googleapis.com/maps/api/place/nearbysearch/json" # ==>> url for the google api + # ==>> url for the google api + places_url = "https://maps.googleapis.com/maps/api/place/nearbysearch/json" places_params = { "pagetoken": page_token, # ==>> pass the page token to the request "key": GOOGLE_API_KEY # ==>> get the google api key from the environment variables } - places_res = requests.get(places_url, params=places_params) # ==>> make a request to the google api with the parameters - if places_res.status_code != 200: # ==>> check if the request was successful + # ==>> make a request to the google api with the parameters + places_res = requests.get(places_url, params=places_params) + if places_res.status_code != 200: # ==>> check if the request was successful return jsonify({"error": "Failed to fetch data from Google", "details": places_res.text}), 500 body = places_res.json() # ==>> parse the response as json raw_places = body.get("results", []) next_page_token = body.get("next_page_token") else: - location = data.get("location") - cocktail = data.get("cocktail") - print(">>> [by-location] STEP 2: location, cocktail =", location, cocktail) - - if not all([location, cocktail]): # ==>> check if all the values are present in the request - print(">>> [by-location] Missing location or cocktail") - return jsonify({"error": "Missing data"}), 400 # ==>> return a 400 error if any of the values are missing - - geo_url = "https://maps.googleapis.com/maps/api/geocode/json" # ==>> Call Geocoding API - geo_params = { + location = data.get("location") + cocktail = data.get("cocktail") + print(">>> [by-location] STEP 2: location, cocktail =", + location, cocktail) + + if not all([location, cocktail]): # ==>> check if all the values are present in the request + print(">>> [by-location] Missing location or cocktail") + # ==>> return a 400 error if any of the values are missing + return jsonify({"error": "Missing data"}), 400 + + # ==>> Call Geocoding API + geo_url = "https://maps.googleapis.com/maps/api/geocode/json" + geo_params = { "address": location, "key": GOOGLE_API_KEY } - geo_res = requests.get(geo_url, params=geo_params) - print(">>> [by-location] STEP 3b: geocode status =", geo_res.status_code) - print(">>> [by-location] geocode body snippet:", geo_res.text[:200], "…") - - if geo_res.status_code != 200: # ==>> check if the request was successful - return jsonify({"error": "Failed to fetch data from Google Geocoding API"}), 500 # ==>> return a 500 error if the request failed - geo_data = geo_res.json() # ==>> # parse JSON into a dict - results = geo_data.get("results", []) # ==>> get the results from the response - if not results: - return jsonify({"error": "No results found"}), 404 # ==>> return a 404 error if there are no results - - location_data = results[0]["geometry"]["location"] # ==>> get the location data from the first result - lat, lng = location_data["lat"], location_data["lng"] # ==>> extract latitude and longitude - print(f">>> [by-location] STEP 4: resolved '{location_data}' → lat={lat}, lng={lng}") - - places_url = "https://maps.googleapis.com/maps/api/place/nearbysearch/json" # Build Nearby Search request - places_params = { + geo_res = requests.get(geo_url, params=geo_params) + print(">>> [by-location] STEP 3b: geocode status =", + geo_res.status_code) + print(">>> [by-location] geocode body snippet:", + geo_res.text[:200], "…") + + if geo_res.status_code != 200: # ==>> check if the request was successful + # ==>> return a 500 error if the request failed + return jsonify({"error": "Failed to fetch data from Google Geocoding API"}), 500 + geo_data = geo_res.json() # ==>> # parse JSON into a dict + # ==>> get the results from the response + results = geo_data.get("results", []) + if not results: + # ==>> return a 404 error if there are no results + return jsonify({"error": "No results found"}), 404 + + # ==>> get the location data from the first result + location_data = results[0]["geometry"]["location"] + # ==>> extract latitude and longitude + lat, lng = location_data["lat"], location_data["lng"] + print( + f">>> [by-location] STEP 4: resolved '{location_data}' → lat={lat}, lng={lng}") + + # Build Nearby Search request + places_url = "https://maps.googleapis.com/maps/api/place/nearbysearch/json" + places_params = { "location": f"{lat},{lng}", "radius": 5000, "type": "bar", "keyword": cocktail, - "key": GOOGLE_API_KEY + "key": GOOGLE_API_KEY } - - if data.get("opennow"): # ==>> check if the opennow parameter is present in the request - places_params["opennow"] = "true" # ==>> add the opennow parameter to the request - print(">>> [by-location] STEP 5: places params =", places_params) - - places_res = requests.get(places_url, params=places_params) - print(">>> [by-location] STEP 5b: places status =", places_res.status_code) - print(">>> [by-location] places body snippet:", places_res.text[:200], "…") - if places_res.status_code != 200: - return jsonify({"error": "Failed to fetch data from Google Places API", "details": places_res.text}), 500 - body = places_res.json() - raw_places = body.get("results", []) - next_page_token = body.get("next_page_token") - print(">>> [by-location] STEP 6: received", len(raw_places), "raw places") - + + if data.get("opennow"): # ==>> check if the opennow parameter is present in the request + # ==>> add the opennow parameter to the request + places_params["opennow"] = "true" + print(">>> [by-location] STEP 5: places params =", places_params) + + places_res = requests.get(places_url, params=places_params) + print(">>> [by-location] STEP 5b: places status =", + places_res.status_code) + print(">>> [by-location] places body snippet:", + places_res.text[:200], "…") + if places_res.status_code != 200: + return jsonify({"error": "Failed to fetch data from Google Places API", "details": places_res.text}), 500 + body = places_res.json() + raw_places = body.get("results", []) + next_page_token = body.get("next_page_token") + print(">>> [by-location] STEP 6: received", + len(raw_places), "raw places") + for place in raw_places: - if place.get("business_status") != "OPERATIONAL": - continue - ref = place.get("photos", [{}])[0].get("photo_reference") # ==>> ↓↓↓get the photo reference of the place.↓↓↓ This accesses the "photos" key in the place dictionary, which is a list of dictionaries. ↓↓↓It tries to get the first dictionary in that list (or an empty dictionary if the list is empty) and then accesses the "photo_reference" key. - if ref: # ==>> check if the photo reference is not empty ## ↑↑↑one representative image per place,↑↑↑ and the first one is usually the best (it’s what Google thinks is most relevant)↑↑↑ - photo_url = ( - f"https://maps.googleapis.com/maps/api/place/photo" # ==>> url for the google api - f"?maxwidth=400" - f"&photoreference={ref}" # ==>> photo reference of the place - f"&key={GOOGLE_API_KEY}" # ==>> google api key - ) - else: - photo_url = None - - filtered_places.append( - { - "name": place.get("name"), - "address": place.get("vicinity") or place.get("formatted_address"), - "rating": place.get("rating"), - "user_ratings_total": place.get("user_ratings_total"), - "location": place["geometry"]["location"], - "place_id": place.get("place_id"), - "photo_url": photo_url - }) - print(f">>> [by-location] STEP 7: filtered down to {len(filtered_places)} bars") + if place.get("business_status") != "OPERATIONAL": + continue + # ==>> ↓↓↓get the photo reference of the place.↓↓↓ This accesses the "photos" key in the place dictionary, which is a list of dictionaries. ↓↓↓It tries to get the first dictionary in that list (or an empty dictionary if the list is empty) and then accesses the "photo_reference" key. + ref = place.get("photos", [{}])[0].get("photo_reference") + # ==>> check if the photo reference is not empty ## ↑↑↑one representative image per place,↑↑↑ and the first one is usually the best (it’s what Google thinks is most relevant)↑↑↑ + if ref: + photo_url = ( + f"https://maps.googleapis.com/maps/api/place/photo" # ==>> url for the google api + f"?maxwidth=400" + f"&photoreference={ref}" # ==>> photo reference of the place + f"&key={GOOGLE_API_KEY}" # ==>> google api key + ) + else: + photo_url = None + + filtered_places.append( + { + "name": place.get("name"), + "address": place.get("vicinity") or place.get("formatted_address"), + "rating": place.get("rating"), + "user_ratings_total": place.get("user_ratings_total"), + "location": place["geometry"]["location"], + "place_id": place.get("place_id"), + "photo_url": photo_url + }) + print( + f">>> [by-location] STEP 7: filtered down to {len(filtered_places)} bars") return jsonify({ "places": filtered_places, "next_page_token": next_page_token }), 200 -@api.route('/places/details', methods=['POST']) # ==>> Endpoint for the Place Details API within the Google Maps Platform. It allows you to request detailed information about a specific place, such as a business or point of interest. +# ==>> Endpoint for the Place Details API within the Google Maps Platform. It allows you to request detailed information about a specific place, such as a business or point of interest. +@api.route('/places/details', methods=['POST']) def get_place_details(): - GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") # ==>> get the google api key from the environment variables - if not GOOGLE_API_KEY: # ==>> check if the google api key is present - return jsonify({"error": "Google API key not found"}), 500 # ==>> return a 500 error if the google api key is missing - data = request.get_json() # ==>> get the data from the request - place_id = data.get("place_id") # ==>> get the place id from the request - - if not place_id: # ==>> check if the place id is present in the request - return jsonify({"error": "Missing place_id"}), 400 # ==>> return a 400 error if the place id is missing - - url = "https://maps.googleapis.com/maps/api/place/details/json" # ==>> url for the google api - params = { - "place_id": place_id, - "fields":"name,formatted_phone_number,opening_hours,website,reviews,photos,formatted_address", # ==>> fields to be returned in the response - "key": GOOGLE_API_KEY - } - res = requests.get(url, params=params) # ==>> make a request to the google api with the parameters - if res.status_code != 200: # ==>> check if the request was successful - return jsonify({"error": "Failed to fetch data from Google", "details": res.text}), 500 # ==>> return a 500 error if the request failed - - body = res.json() # ==>> parse the response as json - details = body.get("result", {}) # Grab the “result” object from the Google response (or an empty dict if it’s missing) - if not details: # ==>> check if the details are present in the response - return jsonify({"error": "No details found"}), 404 # ==>> return a 404 error if the details are missing - - photo_urls = [] # ==>> initialize an empty list for the photo urls - for i in details.get("photos", []): # ==>> iterate over the photos in the details - ref = i.get("photo_reference") # ==>> get the photo reference from the photo - photo_urls.append( - f"https://maps.googleapis.com/maps/api/place/photo" - f"?maxwidth=400" - f"&photoreference={ref}" - f"&key={GOOGLE_API_KEY}" - ) - - return jsonify({ - "name": details.get("name"), - "formatted_address": details.get("formatted_address"), - "formatted_phone_number": details.get("formatted_phone_number", "N/A"), - "opening_hours": details.get("opening_hours", {}), - "website": details.get("website"), - "reviews": details.get("reviews", []), - "photos": photo_urls, # ==>> return the photo urls as a list -}), 200 + # ==>> get the google api key from the environment variables + GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") + if not GOOGLE_API_KEY: # ==>> check if the google api key is present + # ==>> return a 500 error if the google api key is missing + return jsonify({"error": "Google API key not found"}), 500 + data = request.get_json() # ==>> get the data from the request + place_id = data.get("place_id") # ==>> get the place id from the request + + if not place_id: # ==>> check if the place id is present in the request + # ==>> return a 400 error if the place id is missing + return jsonify({"error": "Missing place_id"}), 400 + # ==>> url for the google api + url = "https://maps.googleapis.com/maps/api/place/details/json" + params = { + "place_id": place_id, + # ==>> fields to be returned in the response + "fields": "name,formatted_phone_number,opening_hours,website,reviews,photos,formatted_address", + "key": GOOGLE_API_KEY + } + # ==>> make a request to the google api with the parameters + res = requests.get(url, params=params) + if res.status_code != 200: # ==>> check if the request was successful + # ==>> return a 500 error if the request failed + return jsonify({"error": "Failed to fetch data from Google", "details": res.text}), 500 + body = res.json() # ==>> parse the response as json + # Grab the “result” object from the Google response (or an empty dict if it’s missing) + details = body.get("result", {}) + if not details: # ==>> check if the details are present in the response + # ==>> return a 404 error if the details are missing + return jsonify({"error": "No details found"}), 404 + photo_urls = [] # ==>> initialize an empty list for the photo urls + for i in details.get("photos", []): # ==>> iterate over the photos in the details + # ==>> get the photo reference from the photo + ref = i.get("photo_reference") + photo_urls.append( + f"https://maps.googleapis.com/maps/api/place/photo" + f"?maxwidth=400" + f"&photoreference={ref}" + f"&key={GOOGLE_API_KEY}" + ) + + return jsonify({ + "name": details.get("name"), + "formatted_address": details.get("formatted_address"), + "formatted_phone_number": details.get("formatted_phone_number", "N/A"), + "opening_hours": details.get("opening_hours", {}), + "website": details.get("website"), + "reviews": details.get("reviews", []), + "photos": photo_urls, # ==>> return the photo urls as a list + }), 200 + + +@api.route('/sigin', methods=['POST']) +def sign_in_user(): + data = request.get_json() ## ==>> get the data from the request + if not data: + return jsonify({"error": "Missing data"}), 400 ## ==>> check if the data is present + email = data.get("email") + password = data.get("password") + if not email or not password: + return jsonify({"error": "Missing email or password"}), 400 + user =User.query.filter_by(email=email).first() ## ==>> query the database for the user first value is the column name, second is the value from the request. + if not user: + return jsonify({"error": "User not found"}), 404 + if not check_password_hash(user.password, password): + return jsonify({"error": "Invalid password"}), 401 + diff --git a/src/front/routes.jsx b/src/front/routes.jsx index e6d83d317c..9af62258c6 100644 --- a/src/front/routes.jsx +++ b/src/front/routes.jsx @@ -31,6 +31,7 @@ export const router = createBrowserRouter( } /> } /> } /> + } /> ) ); From c5726b4799fe5d1488422d177b363966dc6af043 Mon Sep 17 00:00:00 2001 From: Jackbringas <102544709+Jackbringas@users.noreply.github.com> Date: Tue, 6 May 2025 01:06:27 +0000 Subject: [PATCH 30/39] create user and encrypted password succesfully --- migrations/versions/6a46316c7cb3_.py | 32 +++++++++++++++++++ src/api/models.py | 2 ++ src/api/routes.py | 47 ++++++++++++++-------------- src/front/pages/GoogleApi.jsx | 2 +- 4 files changed, 59 insertions(+), 24 deletions(-) create mode 100644 migrations/versions/6a46316c7cb3_.py diff --git a/migrations/versions/6a46316c7cb3_.py b/migrations/versions/6a46316c7cb3_.py new file mode 100644 index 0000000000..0019805ac0 --- /dev/null +++ b/migrations/versions/6a46316c7cb3_.py @@ -0,0 +1,32 @@ +"""empty message + +Revision ID: 6a46316c7cb3 +Revises: 1b6206a1749f +Create Date: 2025-05-06 00:48:27.082957 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '6a46316c7cb3' +down_revision = '1b6206a1749f' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('user', schema=None) as batch_op: + batch_op.add_column(sa.Column('name', sa.String(length=120), nullable=False)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('user', schema=None) as batch_op: + batch_op.drop_column('name') + + # ### end Alembic commands ### diff --git a/src/api/models.py b/src/api/models.py index 19317dfbcd..7365ad1929 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -6,6 +6,7 @@ class User(db.Model): id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str]= mapped_column(String(120), nullable=False) email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) password: Mapped[str] = mapped_column(nullable=False) is_active: Mapped[bool] = mapped_column(Boolean(), nullable=False) @@ -16,6 +17,7 @@ class User(db.Model): def serialize(self): return { "id": self.id, + "name": self.name, "email": self.email, # do not serialize the password, its a security breach } diff --git a/src/api/routes.py b/src/api/routes.py index 03be1e3695..4ffb832ba7 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -273,32 +273,33 @@ def get_place_details(): "photos": photo_urls, # ==>> return the photo urls as a list }), 200 -#Jackie +# Jackie @api.route("/signup", methods=["POST"]) def signup(): - name = request.json.get ("name", None) - email = request.json.get("email", None) - password = request.json.get("password", None) + name = request.json.get("name", None) + email = request.json.get("email", None) + password = request.json.get("password", None) - if not name or not email or not password: - return jsonify({"msg": "Name, email and password are required"}), 400 - - user = User.query.filter_by(email=email).first() - if user: - return jsonify({"msg": "User already exists"}),409 - - hashed_password = generate_password_hash(password) - - new_user = User( - name=name, - email=email, - password= hashed_password, - is_active=True - ) - - db.session.add(new_user) - db.session.commit() + if not name or not email or not password: + return jsonify({"msg": "Name, email and password are required"}), 400 + + user = User.query.filter_by(email=email).first() + if user: + return jsonify({"msg": "User already exists"}), 409 + + hashed_password = generate_password_hash(password) + + new_user = User( + name=name, + email=email, + password=hashed_password, + is_active=True + ) + + db.session.add(new_user) + db.session.commit() + + return jsonify("User created successfully"), 201 - return jsonify ("User created sucessfully"),201 diff --git a/src/front/pages/GoogleApi.jsx b/src/front/pages/GoogleApi.jsx index 82511a8a5c..2198c0c8a7 100644 --- a/src/front/pages/GoogleApi.jsx +++ b/src/front/pages/GoogleApi.jsx @@ -1,7 +1,7 @@ import { Link } from "react-router-dom"; import useGlobalReducer from "../hooks/useGlobalReducer"; import { useState } from "react"; -import 'bootstrap-icons/font/bootstrap-icons.css'; +//import 'bootstrap-icons/font/bootstrap-icons.css'; export const GoogleApi = () => { const [places, setPlaces] = useState([]); From 95767ee65a6e4c08dacad7b9b73c3ded9227384a Mon Sep 17 00:00:00 2001 From: XxDrancerxX Date: Tue, 6 May 2025 15:50:48 +0000 Subject: [PATCH 31/39] endpoint --- Pipfile | 1 + Pipfile.lock | 3 ++- src/api/routes.py | 8 ++++++++ src/app.py | 3 +++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Pipfile b/Pipfile index 834a2dde60..8cfda7d8a7 100644 --- a/Pipfile +++ b/Pipfile @@ -21,6 +21,7 @@ sqlalchemy = "*" requests = "*" flask-cors = "*" python-dotenv = "*" +werkzeug = "*" [requires] python_version = "3.13" diff --git a/Pipfile.lock b/Pipfile.lock index 0566464e5b..eacd3b843e 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "bf10612551b9927f533b121b086761c613604aa20a5af737364fcdf45ecb33e0" + "sha256": "e942ca1d51677e09f303df46ce3e18118677e46b7bd0fd08ec9f9ccb795631cb" }, "pipfile-spec": 6, "requires": { @@ -657,6 +657,7 @@ "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746" ], + "index": "pypi", "markers": "python_version >= '3.9'", "version": "==3.1.3" }, diff --git a/src/api/routes.py b/src/api/routes.py index 007729c1b8..4c5360a481 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -26,6 +26,7 @@ CORS(api) + # ==>> this is the endpoint that will be called from the front end # ==>> Search Places by coordinates: Accepts E.G: { latitude: 40.75, longitude: -73.99, cocktail: "Mojito" } @api.route('/places', methods=['POST']) @@ -334,4 +335,11 @@ def sign_in_user(): return jsonify({"error": "User not found"}), 404 if not check_password_hash(user.password, password): return jsonify({"error": "Invalid password"}), 401 + + access_token = create_access_token(identity=user.id) + + return jsonify({ + "token": access_token, + "user": user.serialize() +}), 200 diff --git a/src/app.py b/src/app.py index 691d2f11c5..28062bd278 100644 --- a/src/app.py +++ b/src/app.py @@ -10,6 +10,7 @@ from api.routes import api from api.admin import setup_admin from api.commands import setup_commands +from flask_jwt_extended import JWTManager # from models import Person @@ -18,6 +19,8 @@ os.path.realpath(__file__)), '../public/') app = Flask(__name__) app.url_map.strict_slashes = False +app.config["JWT_SECRET_KEY"] = os.environ.get("JWT_SECRET_KEY") +jwt = JWTManager(app) # database condiguration db_url = os.getenv("DATABASE_URL") From 58fab83e3c4e92455f3c216ffe276715e08ade4a Mon Sep 17 00:00:00 2001 From: Jackbringas <102544709+Jackbringas@users.noreply.github.com> Date: Wed, 7 May 2025 07:00:44 +0000 Subject: [PATCH 32/39] login fetch get a token, logout delete the token --- migrations/versions/1912147c7f92_.py | 32 +++++ src/api/models.py | 3 + src/api/routes.py | 27 +++- src/app.py | 7 +- src/front/components/PrivateRoute.jsx | 7 + src/front/hooks/useGlobalReducer.jsx | 6 +- src/front/pages/Profile.jsx | 31 +++++ src/front/pages/signIn.jsx | 186 +++++++++++++++----------- src/front/routes.jsx | 3 + src/front/store.js | 44 ++++-- 10 files changed, 247 insertions(+), 99 deletions(-) create mode 100644 migrations/versions/1912147c7f92_.py create mode 100644 src/front/components/PrivateRoute.jsx create mode 100644 src/front/pages/Profile.jsx diff --git a/migrations/versions/1912147c7f92_.py b/migrations/versions/1912147c7f92_.py new file mode 100644 index 0000000000..574179025c --- /dev/null +++ b/migrations/versions/1912147c7f92_.py @@ -0,0 +1,32 @@ +"""empty message + +Revision ID: 1912147c7f92 +Revises: 6a46316c7cb3 +Create Date: 2025-05-07 04:41:33.946971 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '1912147c7f92' +down_revision = '6a46316c7cb3' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('user', schema=None) as batch_op: + batch_op.add_column(sa.Column('phone', sa.String(length=20), nullable=False)) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('user', schema=None) as batch_op: + batch_op.drop_column('phone') + + # ### end Alembic commands ### diff --git a/src/api/models.py b/src/api/models.py index 7365ad1929..1586f1a495 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -10,6 +10,8 @@ class User(db.Model): email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) password: Mapped[str] = mapped_column(nullable=False) is_active: Mapped[bool] = mapped_column(Boolean(), nullable=False) + phone: Mapped[str] = mapped_column(String(20)) + @@ -19,6 +21,7 @@ def serialize(self): "id": self.id, "name": self.name, "email": self.email, + "phone": self.phone, # do not serialize the password, its a security breach } diff --git a/src/api/routes.py b/src/api/routes.py index 8b29ddca4c..e901c36609 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -323,7 +323,7 @@ def get_place_details(): }), 200 -@api.route('/sigin', methods=['POST']) +@api.route('/signin', methods=['POST']) def sign_in_user(): data = request.get_json() ## ==>> get the data from the request if not data: @@ -348,9 +348,10 @@ def sign_in_user(): # Jackie @api.route("/signup", methods=["POST"]) def signup(): - name = request.json.get("name", None) - email = request.json.get("email", None) - password = request.json.get("password", None) + name = request.json.get("name") + email = request.json.get("email") + password = request.json.get("password") + phone = request.json.get("phone") if not name or not email or not password: return jsonify({"msg": "Name, email and password are required"}), 400 @@ -361,17 +362,29 @@ def signup(): hashed_password = generate_password_hash(password) + new_user = User( name=name, email=email, password=hashed_password, - is_active=True + is_active=True, + phone=phone ) db.session.add(new_user) - db.session.commit() + try: + db.session.commit() + except Exception as e: + db.session.rollback() + print("Error al guardar usuario:", e) + return jsonify({"msg": "Database error"}), 500 + + return jsonify({"msg": "User created successfully"}), 201 - return jsonify("User created successfully"), 201 +@api.route("/users", methods=["GET"]) +def get_users(): + users = User.query.all() + return jsonify([user.serialize() for user in users]), 200 diff --git a/src/app.py b/src/app.py index 28062bd278..f3bd389174 100644 --- a/src/app.py +++ b/src/app.py @@ -3,6 +3,7 @@ """ import os from flask import Flask, request, jsonify, url_for, send_from_directory +from flask_cors import CORS from flask_migrate import Migrate from flask_swagger import swagger from api.utils import APIException, generate_sitemap @@ -18,6 +19,7 @@ static_file_dir = os.path.join(os.path.dirname( os.path.realpath(__file__)), '../public/') app = Flask(__name__) +CORS(app) app.url_map.strict_slashes = False app.config["JWT_SECRET_KEY"] = os.environ.get("JWT_SECRET_KEY") jwt = JWTManager(app) @@ -41,7 +43,8 @@ setup_commands(app) # Add all endpoints form the API with a "api" prefix -app.register_blueprint(api, url_prefix='/api') #==>With url_prefix='/api', every route defined in your blueprint (like @api.route('/places') in routes.py) becomes reachable at /api/places. +# ==>With url_prefix='/api', every route defined in your blueprint (like @api.route('/places') in routes.py) becomes reachable at /api/places. +app.register_blueprint(api, url_prefix='/api') # Handle/serialize errors like a JSON object @@ -60,6 +63,8 @@ def sitemap(): return send_from_directory(static_file_dir, 'index.html') # any other endpoint will try to serve it like a static file + + @app.route('/', methods=['GET']) def serve_any_other_file(path): if not os.path.isfile(os.path.join(static_file_dir, path)): diff --git a/src/front/components/PrivateRoute.jsx b/src/front/components/PrivateRoute.jsx new file mode 100644 index 0000000000..ba86075f62 --- /dev/null +++ b/src/front/components/PrivateRoute.jsx @@ -0,0 +1,7 @@ +import { Navigate } from "react-router-dom"; +import { useGlobalReducer } from "../hooks/useGlobalReducer"; + +export const PrivateRoute = ({ children }) => { + const { store } = useGlobalReducer(); + return store.token ? children : ; +}; diff --git a/src/front/hooks/useGlobalReducer.jsx b/src/front/hooks/useGlobalReducer.jsx index 6aeb9d768e..28ee35db42 100644 --- a/src/front/hooks/useGlobalReducer.jsx +++ b/src/front/hooks/useGlobalReducer.jsx @@ -18,7 +18,9 @@ export function StoreProvider({ children }) { } // Custom hook to access the global state and dispatch function. -export default function useGlobalReducer() { +export function useGlobalReducer() { const { dispatch, store } = useContext(StoreContext) return { dispatch, store }; -} \ No newline at end of file +} + +export default useGlobalReducer; \ No newline at end of file diff --git a/src/front/pages/Profile.jsx b/src/front/pages/Profile.jsx new file mode 100644 index 0000000000..e6b6e4d454 --- /dev/null +++ b/src/front/pages/Profile.jsx @@ -0,0 +1,31 @@ +import React from "react"; +import { Navigate, useNavigate } from "react-router-dom"; +import useGlobalReducer from "../hooks/useGlobalReducer"; + +export const Profile = () => { + const { store, dispatch } = useGlobalReducer(); + const navigate = useNavigate(); + + const handleLogout = () => { + localStorage.removeItem("token"); + dispatch({ type: "SET_TOKEN", payload: null }); + navigate("/signin"); + }; + + return ( + <> + {store.token ? ( +
+

👋 Welcome!

+

This is your private profile. You are logged in correctly ✅

+ + +
+ ) : ( + + )} + + ); +}; + +export default Profile; diff --git a/src/front/pages/signIn.jsx b/src/front/pages/signIn.jsx index 1f62666bb8..69f54a1f82 100644 --- a/src/front/pages/signIn.jsx +++ b/src/front/pages/signIn.jsx @@ -1,97 +1,125 @@ -import React, {useState} from "react"; - - export const SignIn = () => { - const [formData, setFormData]= useState({ - email: "", +import React, { useState } from "react"; +import useGlobalReducer from "../hooks/useGlobalReducer"; +import { useNavigate} from "react-router-dom"; + +export const SignIn = () => { + const { dispatch } = useGlobalReducer(); + const navigate = useNavigate(); + + const [formData, setFormData] = useState({ + email: "", password: "", }); const [error, setError] = useState(""); - - const handleChange= (e)=> { - setFormData({...formData, [e.target.name]: e.target.value}); + + const handleChange = (e) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); setError(""); }; const handleSubmit = async (e) => { e.preventDefault(); - if(!formData.email || !formData.password){ - setError ("Please complete all fields"); + if (!formData.email || !formData.password) { + setError("Please complete all fields"); return; } - console.log("login sent:", formData); - alert("Login info ready to send"); + try { + console.log(`${import.meta.env.VITE_BACKEND_URL}/api/signin`); - //this is the place for the fetch - // const response = await fetch("BACKEND_URL/login", { ... }) - }; + const response = await fetch(`${import.meta.env.VITE_BACKEND_URL}/api/signin`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(formData), + }); - return ( + const data = await response.json(); + + if (response.ok && data.token) { + localStorage.setItem("token", data.token); + dispatch({ type: "SET_TOKEN", payload: data.token }); + navigate("/profile"); + } else { + setError("Invalid email or password."); + } + } catch (err) { + console.error("Login error:", err); + setError("Invalid email or password."); + } + }; + + return (
- -
-
- Logo -
-
- - -
-

Sign In

- - {error &&( -
- {error} -
- )} - -
- -
- - -
- -
-
- - - - Forgot Password? - - + onSubmit={handleSubmit} + className="container d-flex flex-column align-items-center justify-content-center" + style={{ minHeight: "100vh" }} + > +
+
+ Logo +
+
+ +
+

Sign In

+ + {error && ( +
{error}
+ )} + +
+ +
+ +
+ +
+
+ + + + Forgot Password? +
- ); - }; \ No newline at end of file + ); +}; diff --git a/src/front/routes.jsx b/src/front/routes.jsx index 9af62258c6..770e961224 100644 --- a/src/front/routes.jsx +++ b/src/front/routes.jsx @@ -15,6 +15,8 @@ import { Custom } from "./pages/Custom"; import { SignIn } from "./pages/signIn"; import { Password } from "./pages/password"; import { MainPage } from "./pages/mainpage"; +import { Profile} from "./pages/Profile"; +import { PrivateRoute } from "./components/PrivateRoute"; export const router = createBrowserRouter( createRoutesFromElements( @@ -32,6 +34,7 @@ export const router = createBrowserRouter( } /> } /> } /> + } /> ) ); diff --git a/src/front/store.js b/src/front/store.js index 3062cd222d..10cb71c144 100644 --- a/src/front/store.js +++ b/src/front/store.js @@ -1,5 +1,9 @@ -export const initialStore=()=>{ - return{ +import React, { useReducer, createContext } from "react"; + + +export const initialStore = () => { + return { + token: localStorage.getItem("token") || null, message: null, todos: [ { @@ -13,26 +17,46 @@ export const initialStore=()=>{ background: null, } ] - } -} + }; +}; export default function storeReducer(store, action = {}) { - switch(action.type){ + switch (action.type) { case 'set_hello': return { ...store, message: action.payload }; - - case 'add_task': - const { id, color } = action.payload + case 'add_task': + const { id, color } = action.payload; + return { + ...store, + todos: store.todos.map(todo => + todo.id === id ? { ...todo, background: color } : todo + ) + }; + case 'SET_TOKEN': return { ...store, - todos: store.todos.map((todo) => (todo.id === id ? { ...todo, background: color } : todo)) + token: action.payload }; + default: throw Error('Unknown action.'); - } + } } + + +export const Context = createContext(); + +export const StoreProvider = ({ children }) => { + const [store, dispatch] = useReducer(storeReducer, initialStore()); + + return React.createElement( + Context.Provider, + { value: { store, dispatch } }, + children + ); +}; From d35c555a5c1b3656f9361dc996696fa10c671d68 Mon Sep 17 00:00:00 2001 From: Jackbringas <102544709+Jackbringas@users.noreply.github.com> Date: Wed, 7 May 2025 07:16:11 +0000 Subject: [PATCH 33/39] error message changed --- src/api/routes.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/api/routes.py b/src/api/routes.py index e901c36609..a124638a40 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -28,7 +28,6 @@ CORS(api) - # ==>> this is the endpoint that will be called from the front end # ==>> Search Places by coordinates: Accepts E.G: { latitude: 40.75, longitude: -73.99, cocktail: "Mojito" } @api.route('/places', methods=['POST']) @@ -325,33 +324,37 @@ def get_place_details(): @api.route('/signin', methods=['POST']) def sign_in_user(): - data = request.get_json() ## ==>> get the data from the request + data = request.get_json() # ==>> get the data from the request if not data: - return jsonify({"error": "Missing data"}), 400 ## ==>> check if the data is present + # ==>> check if the data is present + return jsonify({"error": "Missing data"}), 400 email = data.get("email") password = data.get("password") if not email or not password: return jsonify({"error": "Missing email or password"}), 400 - user =User.query.filter_by(email=email).first() ## ==>> query the database for the user first value is the column name, second is the value from the request. + # ==>> query the database for the user first value is the column name, second is the value from the request. + user = User.query.filter_by(email=email).first() if not user: return jsonify({"error": "User not found"}), 404 if not check_password_hash(user.password, password): return jsonify({"error": "Invalid password"}), 401 - + access_token = create_access_token(identity=user.id) return jsonify({ - "token": access_token, - "user": user.serialize() -}), 200 + "token": access_token, + "user": user.serialize() + }), 200 # Jackie + + @api.route("/signup", methods=["POST"]) def signup(): name = request.json.get("name") email = request.json.get("email") password = request.json.get("password") - phone = request.json.get("phone") + phone = request.json.get("phone") if not name or not email or not password: return jsonify({"msg": "Name, email and password are required"}), 400 @@ -362,13 +365,12 @@ def signup(): hashed_password = generate_password_hash(password) - new_user = User( name=name, email=email, password=hashed_password, is_active=True, - phone=phone + phone=phone ) db.session.add(new_user) @@ -376,7 +378,7 @@ def signup(): db.session.commit() except Exception as e: db.session.rollback() - print("Error al guardar usuario:", e) + print("Error saving user:", e) return jsonify({"msg": "Database error"}), 500 return jsonify({"msg": "User created successfully"}), 201 @@ -386,5 +388,3 @@ def signup(): def get_users(): users = User.query.all() return jsonify([user.serialize() for user in users]), 200 - - From e220e196e8da77dbe722c54118c5d3273ab5b6a6 Mon Sep 17 00:00:00 2001 From: XxDrancerxX Date: Wed, 7 May 2025 17:36:16 +0000 Subject: [PATCH 34/39] SIGNUPDONE --- .vscode/settings.json | 22 ++-- migrations/versions/57561960a0c5_.py | 34 +++++ src/api/models.py | 3 + src/api/routes.py | 8 +- src/front/pages/signUp.jsx | 190 ++++++++++++++++----------- 5 files changed, 167 insertions(+), 90 deletions(-) create mode 100644 migrations/versions/57561960a0c5_.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 246b0419d0..37ade16d4b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,16 @@ { - "editor.formatOnSave": true, - "editor.defaultFormatter": "esbenp.prettier-vscode", - "workbench.editorAssociations": { - "*.md": "vscode.markdown.preview.editor" - }, - "[javascriptreact]": { - "editor.defaultFormatter": "vscode.typescript-language-features" - } + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "workbench.editorAssociations": { + "*.md": "vscode.markdown.preview.editor" + }, + "[javascriptreact]": { + "editor.defaultFormatter": "vscode.typescript-language-features" + }, + "github.copilot.enable": { + "*": true, + "plaintext": false, + "markdown": false, + "scminput": false + } } diff --git a/migrations/versions/57561960a0c5_.py b/migrations/versions/57561960a0c5_.py new file mode 100644 index 0000000000..642c24b203 --- /dev/null +++ b/migrations/versions/57561960a0c5_.py @@ -0,0 +1,34 @@ +"""empty message + +Revision ID: 57561960a0c5 +Revises: 6a46316c7cb3 +Create Date: 2025-05-07 14:55:24.074779 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '57561960a0c5' +down_revision = '6a46316c7cb3' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('user', schema=None) as batch_op: + batch_op.add_column(sa.Column('phone', sa.String(length=20), nullable=False)) + batch_op.create_unique_constraint(None, ['phone']) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('user', schema=None) as batch_op: + batch_op.drop_constraint(None, type_='unique') + batch_op.drop_column('phone') + + # ### end Alembic commands ### diff --git a/src/api/models.py b/src/api/models.py index 7365ad1929..29d04fe084 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -9,7 +9,9 @@ class User(db.Model): name: Mapped[str]= mapped_column(String(120), nullable=False) email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) password: Mapped[str] = mapped_column(nullable=False) + phone : Mapped[str] = mapped_column(String(20), unique=True, nullable=False) is_active: Mapped[bool] = mapped_column(Boolean(), nullable=False) + @@ -19,6 +21,7 @@ def serialize(self): "id": self.id, "name": self.name, "email": self.email, + "phone": self.phone # do not serialize the password, its a security breach } diff --git a/src/api/routes.py b/src/api/routes.py index 8b29ddca4c..415334796c 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -323,7 +323,7 @@ def get_place_details(): }), 200 -@api.route('/sigin', methods=['POST']) +@api.route('/signin', methods=['POST']) def sign_in_user(): data = request.get_json() ## ==>> get the data from the request if not data: @@ -351,9 +351,10 @@ def signup(): name = request.json.get("name", None) email = request.json.get("email", None) password = request.json.get("password", None) + phone = request.json.get("phone", None) - if not name or not email or not password: - return jsonify({"msg": "Name, email and password are required"}), 400 + if not name or not email or not password or not phone: + return jsonify({"msg": "Name, email, phone and password are required"}), 400 user = User.query.filter_by(email=email).first() if user: @@ -364,6 +365,7 @@ def signup(): new_user = User( name=name, email=email, + phone=phone, password=hashed_password, is_active=True ) diff --git a/src/front/pages/signUp.jsx b/src/front/pages/signUp.jsx index 845c890532..bcaaacca9c 100644 --- a/src/front/pages/signUp.jsx +++ b/src/front/pages/signUp.jsx @@ -2,103 +2,135 @@ import React, { useState } from "react"; export const SignUp = () => { - const [formData, setFormData]=useState({ - name: "", + const [formData, setFormData] = useState({ + name: "", email: "", password: "", phone: "" }); - const handleChange = (e)=>{ - setFormData ({...formData, [e.target.name]: e.target.value}); + const handleChange = (e) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); }; const handleSubmit = async (e) => { - e.preventDefault(); - //need to update with fetch when be ready// - console.log ("Form sent:",formData); - alert("Sign Up info ready to send"); + e.preventDefault(); // stops page to reload + console.log(">>>Form Data to be sent!!!:", formData); + try { + const fetchSignUp = await fetch(`${import.meta.env.VITE_BACKEND_URL}/api/signup`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(formData) // + }) + console.log(">>>Response Status!!!:", fetchSignUp.status); + + + const data = await fetchSignUp.json() + console.log(">>>Response Body!!!!:", data); + if (fetchSignUp.ok) { + alert("Sign Up Successful!") + setFormData( // reset form data + { + name: "", + email: "", + password: "", + phone: "" + }) + } + else { + alert("Sign Up failed: " + data.msg); + setFormData({ + name: "", + email: "", + password: "", + phone: "" + }) // clear password field + } + } + catch (err) { + console.log("Error:", err); + } }; - + return ( -
-
- -
-
+ +
+
- Logo -
-
- - -
-

Sign Up

- -
- + style={{ width: 80, height: 80, color: "white", fontWeight: "bold" }} + > + Logo +
-
- -
-
- -
+
+

Sign Up

+ +
+ +
-
- +
+ +
+ +
+ +
+ +
+ +
-
- - - -
+ + + +
); }; From 42f0ffef04992389a472e8a1b5668dd8d3ca6f49 Mon Sep 17 00:00:00 2001 From: XxDrancerxX Date: Wed, 7 May 2025 20:13:18 +0000 Subject: [PATCH 35/39] updated --- src/api/routes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/api/routes.py b/src/api/routes.py index 71f3a44702..56b8ac326f 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -371,7 +371,6 @@ def signup(): phone=phone, password=hashed_password, is_active=True, - phone=phone ) db.session.add(new_user) From 055d3c0b690222ccef232d442e56ab8c0558f8a8 Mon Sep 17 00:00:00 2001 From: Wdrew232 Date: Wed, 7 May 2025 23:27:56 +0000 Subject: [PATCH 36/39] fixed coding and added custom cocktails --- src/front/index.css | 267 +++++++------------------------------ src/front/pages/Custom.jsx | 148 +++++++++----------- 2 files changed, 111 insertions(+), 304 deletions(-) diff --git a/src/front/index.css b/src/front/index.css index 5cd7e80d4a..1525a3b600 100644 --- a/src/front/index.css +++ b/src/front/index.css @@ -1,265 +1,94 @@ -/* ✅ Custom Component Styles */ +/* General Styles */ .custom-app { font-family: Arial, sans-serif; - padding: 20px; text-align: center; + padding: 20px; + background-color: #f8f9fa; } -.custom-cocktail-list { +/* Ingredient List */ +.custom-ingredient-list { display: flex; flex-wrap: wrap; - gap: 20px; justify-content: center; + gap: 20px; + margin-top: 20px; } -.custom-drink-card { - background-color: #f9f9f9; - border: 1px solid #ddd; +/* Ingredient Card */ +.ingredient-card { + background-color: white; + border-radius: 10px; padding: 15px; - border-radius: 5px; - width: 250px; -} - -.custom-drink-card h2 { - font-size: 20px; - margin-bottom: 10px; -} - -.custom-modal { - position: fixed; - top: 50%; - left: 50%; - width: 300px; - padding: 20px; - background: white; - border-radius: 5px; - transform: translate(-50%, -50%); - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); -} - -.custom-ingredient { - display: flex; - align-items: center; - gap: 10px; - margin-bottom: 5px; -} - -.custom-matches { - margin-top: 10px; - font-weight: bold; + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); + width: 200px; + text-align: center; } -.custom-save-btn, -.custom-close-btn { +.ingredient-card img { + width: 100px; + border-radius: 10px; margin-top: 10px; - padding: 10px; - border: none; - background-color: #007bff; - color: white; - cursor: pointer; - border-radius: 3px; -} - -.custom-save-btn:hover, -.custom-close-btn:hover { - background-color: #0056b3; -} -body { - margin: 0; - padding: 0; - background: linear-gradient(135deg, #a1c4fd, #c2e9fb); /* Gradient background */ - font-family: 'Arial', sans-serif; - display: flex; - justify-content: center; - align-items: center; - min-height: 100vh; /* Full screen height */ - color: #333; /* Text color */ -} - -h1, h2, h3 { - font-family: 'Helvetica', sans-serif; - color: #444; /* Darker text color for headers */ -} - -button { - background-color: #007bff; /* Button background */ - color: #fff; /* Button text */ - border: none; - border-radius: 5px; - padding: 10px 15px; - cursor: pointer; - font-size: 1rem; - transition: background-color 0.3s, transform 0.3s; -} - -button:hover { - background-color: #0056b3; /* Hover background */ - transform: scale(1.05); /* Slight zoom on hover */ -} -/* .Cocktail { - display: flex; - flex-wrap: wrap; - gap: 20px; - justify-content: center; } -.drink { - width: 150px; - padding: 10px; - border: 1px solid #ddd; - border-radius: 8px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - text-align: center; - background: white; - transition: transform 0.2s; -} - -.drink img { - border-radius: 50%; +.ingredient-card h2 { + font-size: 18px; margin-bottom: 10px; } -.drink div { +/* Checkbox Styling */ +.ingredient-checkbox { margin-top: 10px; display: flex; - flex-direction: column; - align-items: center; -} - -.drink input[type="checkbox"] { - margin-bottom: 5px; -} - -.drink:hover { - transform: scale(1.05); - box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); -} */ -/* Container for the search functionality */ -.search-container { - display: flex; - flex-direction: column; align-items: center; justify-content: center; - padding: 20px; - background: rgba(255, 255, 255, 0.8); - border-radius: 10px; - box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); - max-width: 600px; - margin: auto; } -/* Search Bar */ -.search-bar { - display: flex; - width: 100%; - gap: 10px; +.ingredient-checkbox input { + margin-right: 10px; } -/* Search Input */ -.search-input { - flex: 1; - padding: 10px; - border: 2px solid #007bff; - border-radius: 5px; - font-size: 1rem; - transition: border-color 0.3s, box-shadow 0.3s; -} - -.search-input:focus { - border-color: #0056b3; - box-shadow: 0px 0px 8px rgba(0, 123, 255, 0.5); - outline: none; -} - -/* Search Button */ -.search-button { - padding: 10px 15px; - background-color: #007bff; +/* Create Cocktail Button */ +.create-cocktail-btn { + background-color: #28a745; color: white; - font-size: 1rem; border: none; - border-radius: 5px; + padding: 10px 15px; + font-size: 16px; cursor: pointer; - transition: background 0.3s, transform 0.2s; -} - -.search-button:hover { - background-color: #0056b3; - transform: scale(1.05); -} - -.search-button:active { - background-color: #004494; - transform: scale(0.95); + border-radius: 5px; + margin-top: 20px; } -/* Cocktail List */ -.cocktail-list { - display: flex; - flex-wrap: wrap; - gap: 20px; - justify-content: center; - margin-top: 20px; +.create-cocktail-btn:hover { + background-color: #218838; } -/* Individual Cocktail Cards */ +/* Cocktail Card */ .cocktail-card { - width: 250px; + background-color: white; + border-radius: 10px; padding: 15px; - border: 1px solid #ddd; - border-radius: 8px; - box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1); - background: white; + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); + width: 250px; text-align: center; - transition: transform 0.2s; -} - -.cocktail-card:hover { - transform: scale(1.05); + margin: 20px auto; } -/* Cocktail Titles */ -.cocktail-title { - font-size: 1.5rem; - color: #333; - margin-bottom: 10px; -} - -/* Cocktail Images */ -.cocktail-image { - width: 100%; - border-radius: 8px; +.cocktail-card h2 { + font-size: 20px; margin-bottom: 10px; } -/* Glass and Category */ -.cocktail-glass, .cocktail-category { - font-size: 1rem; - color: #555; -} - -/* Ingredients List */ -.ingredient-list { - list-style: none; +.cocktail-card ul { + list-style-type: none; padding: 0; - text-align: left; -} - -.ingredient-item { - font-size: 0.9rem; - color: #555; } -/* Instructions */ -.cocktail-instructions { - font-size: 1rem; - color: #444; -} - -/* No Results Message */ -.no-results { - font-size: 1.2rem; - color: red; - margin-top: 20px; +.cocktail-card ul li { + background-color: #ff6f61; + color: white; + padding: 5px; + margin: 3px; + border-radius: 5px; } - diff --git a/src/front/pages/Custom.jsx b/src/front/pages/Custom.jsx index d918da85bc..6c7afc1022 100644 --- a/src/front/pages/Custom.jsx +++ b/src/front/pages/Custom.jsx @@ -1,17 +1,12 @@ import React, { useState, useEffect } from "react"; -import "./Custom.css"; // ✅ Import unique CSS + const handleFetch = (setIngredients) => { - fetch("https://www.thecocktaildb.com/api/json/v1/1/list.php?i=list") + fetch("https://www.thecocktaildb.com/api/json/v1/1/list.php?i=list") // Fetch all ingredients .then((res) => res.json()) .then((data) => { if (data.drinks) { - // Add placeholder images for each ingredient - const ingredientsWithImages = data.drinks.map((drink) => ({ - ...drink, - image: `https://www.thecocktaildb.com/images/ingredients/${drink.strIngredient1}-Medium.png`, - })); - setIngredients(ingredientsWithImages); + setIngredients(data.drinks.map((drink) => drink.strIngredient1)); // Extract ingredient names } else { setIngredients([]); } @@ -19,29 +14,24 @@ const handleFetch = (setIngredients) => { .catch((err) => console.error(err)); }; +// Function to generate a random cocktail name +const generateCocktailName = () => { + const adjectives = ["Zesty", "Smooth", "Fiery", "Refreshing", "Bold", "Exotic", "Mystic", "Golden"]; + const nouns = ["Sunset", "Storm", "Delight", "Twist", "Fusion", "Bliss", "Sensation", "Elixir"]; + + const randomAdjective = adjectives[Math.floor(Math.random() * adjectives.length)]; + const randomNoun = nouns[Math.floor(Math.random() * nouns.length)]; + + return `${randomAdjective} ${randomNoun}`; +}; + export const Custom = () => { const [ingredients, setIngredients] = useState([]); const [selectedIngredients, setSelectedIngredients] = useState([]); - const [matches, setMatches] = useState(0); - const [isModalOpen, setIsModalOpen] = useState(false); + const [cocktailCreated, setCocktailCreated] = useState(null); useEffect(() => { - handleFetch(setDrinks); - const extractIngredients = (cocktails) => { - const allIngredients = cocktails.reduce((acc, drink) => { - [...Array(15).keys()].forEach((i) => { - const ingredient = drink[`strIngredient${i + 1}`]; - if (ingredient && !acc.includes(ingredient)) { - acc.push(ingredient); - } - }); - return acc; - }, []); - setIngredients(allIngredients); - }; - handleFetch((cocktails) => { - extractIngredients(cocktails); - }); + handleFetch(setIngredients); }, []); const handleIngredientToggle = (ingredient) => { @@ -50,77 +40,65 @@ export const Custom = () => { : [...selectedIngredients, ingredient]; setSelectedIngredients(newSelected); - - // Calculate matches - const matchCount = drinks.filter((drink) => - newSelected.every((ing) => { - return [...Array(15).keys()] - .map((i) => drink[`strIngredient${i + 1}`]) - .includes(ing); - }) - ).length; - setMatches(matchCount); }; - const saveCustomSet = () => { - const customSet = { - name: `Custom Set ${new Date().toISOString()}`, + const createCocktail = () => { + if (selectedIngredients.length === 0) { + alert("Please select at least one ingredient to create a cocktail!"); + return; + } + + setCocktailCreated({ + name: generateCocktailName(), // Generate random cocktail name ingredients: selectedIngredients, - }; - const savedSets = JSON.parse(localStorage.getItem("customSets")) || []; - localStorage.setItem("customSets", JSON.stringify([...savedSets, customSet])); - setIsModalOpen(false); + }); }; return (
- +

Ingredients List

- {/* Cocktail list */} -
- {drinks.length > 0 && drinks && drinks !== "no data found" ? - drinks.map((drink) => ( -
-

{drink.strIngredient1}

- {/* {drink.strDrink} -

Glass: {drink.strGlass}

-

Category: {drink.strCategory}

-

Ingredients:

-
    - {[...Array(15).keys()].map((i) => { - const ingredient = drink[`strIngredient${i + 1}`]; - return ingredient &&
  • {ingredient}
  • ; - })} -
-

Instructions: {drink.strInstructions}

*/} +
+ {ingredients.length > 0 ? ( + ingredients.map((ingredient) => ( +
+

{ingredient}

+ {ingredient} +
+ handleIngredientToggle(ingredient)} + /> + +
- ) - ) - : - "No Drinks availabale!!!" - - } + )) + ) : ( + "No Ingredients available!!!" + )}
- {/* Modal for selecting ingredients */} - {isModalOpen && ( -
-

Select Your Ingredients

- {ingredients.map((ingredient) => ( -
- handleIngredientToggle(drink.strIngredient1)} - /> - -
- ))} -
Matches: {matches} cocktails
- - + {/* Button to create cocktail */} + + + {/* Display created cocktail */} + {cocktailCreated && ( +
+

{cocktailCreated.name}

+

Ingredients:

+
    + {cocktailCreated.ingredients.map((ingredient, index) => ( +
  • {ingredient}
  • + ))} +
)}
); -}; \ No newline at end of file +}; From b107334f785bf07629f42ba18c172b07399071eb Mon Sep 17 00:00:00 2001 From: Wdrew232 Date: Wed, 7 May 2025 23:54:57 +0000 Subject: [PATCH 37/39] created favorites page --- src/front/index.css | 89 +++++++++++++++++++++++++++++++++++ src/front/pages/Favorites.jsx | 49 +++++++++++++++++++ src/front/routes.jsx | 2 + 3 files changed, 140 insertions(+) create mode 100644 src/front/pages/Favorites.jsx diff --git a/src/front/index.css b/src/front/index.css index 7dee39bcda..0b1c2f24d3 100644 --- a/src/front/index.css +++ b/src/front/index.css @@ -246,4 +246,93 @@ button:hover { color: red; margin-top: 20px; } +/* General Styles */ +.favorites-app { + font-family: Arial, sans-serif; + padding: 20px; + background-color: #f8f9fa; + text-align: center; + position: relative; +} + +/* User Info Section (Now Positioned to the Left) */ +.user-info { + position: absolute; + top: 100px; /* Adjust based on navbar height */ + left: 20px; + background-color: white; + padding: 15px; + border-radius: 8px; + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); + width: 200px; + text-align: left; +} + +.user-info h3 { + margin: 0; + font-size: 18px; +} + +.user-info p { + margin: 5px 0; + font-size: 14px; + color: gray; +} + +/* Favorites Banner */ +h1 { + font-size: 24px; + margin-bottom: 20px; +} + +/* Favorites List */ +.favorites-list { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 20px; + margin-top: 20px; +} + +/* Favorite Card */ +.favorite-card { + background-color: white; + border-radius: 10px; + padding: 15px; + box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); + width: 250px; + text-align: center; +} + +.favorite-card img { + width: 100px; + border-radius: 10px; + margin-top: 10px; +} + +.favorite-card h2 { + font-size: 20px; + margin-bottom: 10px; +} + +.favorite-card p { + font-size: 16px; + margin: 5px 0; +} + +/* Remove Favorite Button */ +.remove-favorite-btn { + background-color: #dc3545; + color: white; + border: none; + padding: 10px 15px; + font-size: 16px; + cursor: pointer; + border-radius: 5px; + margin-top: 10px; +} + +.remove-favorite-btn:hover { + background-color: #c82333; +} diff --git a/src/front/pages/Favorites.jsx b/src/front/pages/Favorites.jsx new file mode 100644 index 0000000000..a199589ce7 --- /dev/null +++ b/src/front/pages/Favorites.jsx @@ -0,0 +1,49 @@ +import React, { useState, useEffect } from "react"; + + +export const Favorites = () => { + const [favorites, setFavorites] = useState([]); + const [user, setUser] = useState({ name: "John Doe", email: "john@example.com" }); + + useEffect(() => { + // Load favorites from localStorage or API + const savedFavorites = JSON.parse(localStorage.getItem("favorites")) || []; + setFavorites(savedFavorites); + }, []); + + const removeFavorite = (item) => { + const updatedFavorites = favorites.filter((fav) => fav.id !== item.id); + setFavorites(updatedFavorites); + localStorage.setItem("favorites", JSON.stringify(updatedFavorites)); + }; + + return ( +
+ {/* User Info Section */} +
+

{user.name}

+

{user.email}

+
+ +

My Favorites

+ +
+ {favorites.length > 0 ? ( + favorites.map((item) => ( +
+

{item.name}

+ {item.name} +

Category: {item.category || "N/A"}

+

Description: {item.description || "No description available."}

+ +
+ )) + ) : ( +

No favorites added yet!

+ )} +
+
+ ); +}; diff --git a/src/front/routes.jsx b/src/front/routes.jsx index 770e961224..a9c67472da 100644 --- a/src/front/routes.jsx +++ b/src/front/routes.jsx @@ -17,6 +17,7 @@ import { Password } from "./pages/password"; import { MainPage } from "./pages/mainpage"; import { Profile} from "./pages/Profile"; import { PrivateRoute } from "./components/PrivateRoute"; +import { Favorites } from "./pages/Favorites"; export const router = createBrowserRouter( createRoutesFromElements( @@ -35,6 +36,7 @@ export const router = createBrowserRouter( } /> } /> } /> + } /> ) ); From dce7910fb220c901ea4900da65c3413d69c21055 Mon Sep 17 00:00:00 2001 From: Wdrew232 Date: Thu, 8 May 2025 00:58:19 +0000 Subject: [PATCH 38/39] added local storage to favorites that get clicked on from search --- src/front/pages/Favorites.jsx | 59 +++++++++++++++++------------------ 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/src/front/pages/Favorites.jsx b/src/front/pages/Favorites.jsx index a199589ce7..74c4f9cf4b 100644 --- a/src/front/pages/Favorites.jsx +++ b/src/front/pages/Favorites.jsx @@ -1,49 +1,46 @@ import React, { useState, useEffect } from "react"; - export const Favorites = () => { const [favorites, setFavorites] = useState([]); - const [user, setUser] = useState({ name: "John Doe", email: "john@example.com" }); + // Load favorites from local storage on mount useEffect(() => { - // Load favorites from localStorage or API - const savedFavorites = JSON.parse(localStorage.getItem("favorites")) || []; - setFavorites(savedFavorites); + const storedFavorites = JSON.parse(localStorage.getItem("favorites")) || []; + setFavorites(storedFavorites); }, []); - const removeFavorite = (item) => { - const updatedFavorites = favorites.filter((fav) => fav.id !== item.id); + // Remove favorite and update local storage + const removeFavorite = (drinkId) => { + const updatedFavorites = favorites.filter(fav => fav.idDrink !== drinkId); setFavorites(updatedFavorites); localStorage.setItem("favorites", JSON.stringify(updatedFavorites)); }; return ( -
- {/* User Info Section */} -
-

{user.name}

-

{user.email}

-
- -

My Favorites

- -
- {favorites.length > 0 ? ( - favorites.map((item) => ( -
-

{item.name}

- {item.name} -

Category: {item.category || "N/A"}

-

Description: {item.description || "No description available."}

-
- )) - ) : ( -

No favorites added yet!

- )} -
+ ))} +
+ ) : ( +

You haven't added any favorites yet.

+ )}
); }; From cbfac032e542a8aa75f3c9356fcf05de9c3d87b2 Mon Sep 17 00:00:00 2001 From: Wdrew232 Date: Thu, 8 May 2025 01:19:33 +0000 Subject: [PATCH 39/39] set up storage for favorites --- src/front/index.css | 17 ++++++--- src/front/pages/Favorites.jsx | 67 +++++++++++++++++++++++------------ 2 files changed, 57 insertions(+), 27 deletions(-) diff --git a/src/front/index.css b/src/front/index.css index 7883ebb260..7f9f40cd92 100644 --- a/src/front/index.css +++ b/src/front/index.css @@ -144,19 +144,21 @@ button:hover { padding: 20px; background-color: #f8f9fa; text-align: center; - position: relative; + display: flex; + flex-direction: row; + min-height: 100vh; } -/* User Info Section (Now Positioned to the Left) */ +/* User Info Section (Fixed to the Left) */ .user-info { - position: absolute; + position: fixed; top: 100px; /* Adjust based on navbar height */ left: 20px; background-color: white; padding: 15px; border-radius: 8px; box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1); - width: 200px; + width: 220px; text-align: left; } @@ -171,6 +173,13 @@ button:hover { color: gray; } +/* Favorites Content (Ensuring Proper Spacing) */ +.favorites-content { + margin-left: 250px; /* Adjust to prevent overlap with user info */ + flex-grow: 1; + padding: 20px; +} + /* Favorites Banner */ h1 { font-size: 24px; diff --git a/src/front/pages/Favorites.jsx b/src/front/pages/Favorites.jsx index 74c4f9cf4b..8def43c278 100644 --- a/src/front/pages/Favorites.jsx +++ b/src/front/pages/Favorites.jsx @@ -1,5 +1,20 @@ import React, { useState, useEffect } from "react"; +const UserInfo = () => { + // Example user data (Replace with actual user data from local storage or backend) + const user = JSON.parse(localStorage.getItem("user")) || { + name: "John Doe", + email: "johndoe@example.com", + }; + + return ( +
+

{user.name}

+

{user.email}

+
+ ); +}; + export const Favorites = () => { const [favorites, setFavorites] = useState([]); @@ -18,29 +33,35 @@ export const Favorites = () => { return (
-

My Favorite Cocktails

- {favorites.length > 0 ? ( -
- {favorites.map((drink) => ( -
-

{drink.strDrink}

- {drink.strDrink} -

Glass: {drink.strGlass}

-

Category: {drink.strCategory}

- - {/* Remove Favorite Button */} - -
- ))} -
- ) : ( -

You haven't added any favorites yet.

- )} + {/* Sidebar for User Info */} + + + {/* Favorites List */} +
+

My Favorite Cocktails

+ {favorites.length > 0 ? ( +
+ {favorites.map((drink) => ( +
+

{drink.strDrink}

+ {drink.strDrink} +

Glass: {drink.strGlass}

+

Category: {drink.strCategory}

+ + {/* Remove Favorite Button */} + +
+ ))} +
+ ) : ( +

You haven't added any favorites yet.

+ )} +
); };