diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..856f0d179
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,13 @@
+.env
+
+# SQLite databases
+*.db
+
+# VSCode settings
+.vscode/
+
+# Logs
+*.log
+
+# modules
+node_modules
diff --git a/README.md b/README.md
index 0e1211217..d0bd9b6b3 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,14 @@
-# [your app name here]
+# EatRite
CodePath WEB103 Final Project
-Designed and developed by: [your names here]
+Designed and developed by:
+Ashok Gaire,
+Ousseynou Diop,
+Mohammad Naeem,
+Khadeja Ahmar,
+Oscar Mochizaki,
+Noah Mckegney
🔗 Link to deployed app:
@@ -10,35 +16,62 @@ Designed and developed by: [your names here]
### Description and Purpose
-[text goes here]
+The purpose of the App is to create meal plans based on specified available ingredients. Users can enter ingredients they already have at home, set dietary preferences, and get recipe suggestions. Users could also save favorite recipes to their profile.
### Inspiration
-[text goes here]
+Many people want to eat healthier or add more variety to their meals, but coming up with new ideas for breakfast, lunch, dinner, or snacks can be difficult. People often fall back on the same recipes they already know or spend time searching through multiple websites for inspiration. This app helps solve that problem by generating personalized recipe suggestions based on ingredients users already have on hand while also considering their dietary preferences.
## Tech Stack
-Frontend:
+Frontend: React
-Backend:
+Backend: Express
## Features
-### [Name of Feature 1]
+### Home page ✅
-[short description goes here]
+- Recipe results page
+- Shows all available recipes
+- Sorts them based on star rating
+- Can ajust results: new, favorite, etc.
-[gif goes here]
+[](https://www.youtube.com/watch?v=sg9T2A7yaAI)
+
+### User kitchen Page ✅
+
+- Cards with all Ingredients in users kitchen
+- Link to page to input new ingredients
+
+[](https://youtu.be/fpdyUssrY04)
+
+### Ingredients input page ✅
-### [Name of Feature 2]
+- Ingredient input form
+- linked from User kitchen page
+- Metrics for dietary info
-[short description goes here]
+[](https://youtu.be/x-i2w46ggYo)
+
+### User Profile
+
+- Link to Page to edit Ditary preferences
+- Link to User kitchen Page
+- User Logo
[gif goes here]
-### [Name of Feature 3]
+### User Authentication ✅
+
+- login page
+
+[](https://youtu.be/mr_CoBDzWKs)
+
+### Meal Calendar
-[short description goes here]
+- Tracks what you ate on what days
+- Can schedule meals
[gif goes here]
diff --git a/client/.gitignore b/client/.gitignore
new file mode 100644
index 000000000..a547bf36d
--- /dev/null
+++ b/client/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/client/README.md b/client/README.md
new file mode 100644
index 000000000..a36934d87
--- /dev/null
+++ b/client/README.md
@@ -0,0 +1,16 @@
+# React + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
+
+## React Compiler
+
+The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
diff --git a/client/eslint.config.js b/client/eslint.config.js
new file mode 100644
index 000000000..ea36dd3dc
--- /dev/null
+++ b/client/eslint.config.js
@@ -0,0 +1,21 @@
+import js from '@eslint/js'
+import globals from 'globals'
+import reactHooks from 'eslint-plugin-react-hooks'
+import reactRefresh from 'eslint-plugin-react-refresh'
+import { defineConfig, globalIgnores } from 'eslint/config'
+
+export default defineConfig([
+ globalIgnores(['dist']),
+ {
+ files: ['**/*.{js,jsx}'],
+ extends: [
+ js.configs.recommended,
+ reactHooks.configs.flat.recommended,
+ reactRefresh.configs.vite,
+ ],
+ languageOptions: {
+ globals: globals.browser,
+ parserOptions: { ecmaFeatures: { jsx: true } },
+ },
+ },
+])
diff --git a/client/index.html b/client/index.html
new file mode 100644
index 000000000..6e56e6e40
--- /dev/null
+++ b/client/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ client
+
+
+
+
+
+
diff --git a/client/package-lock.json b/client/package-lock.json
new file mode 100644
index 000000000..42e03e8f1
--- /dev/null
+++ b/client/package-lock.json
@@ -0,0 +1,2485 @@
+{
+ "name": "client",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "client",
+ "version": "0.0.0",
+ "dependencies": {
+ "react": "^19.2.7",
+ "react-dom": "^19.2.7",
+ "react-router-dom": "^7.18.1"
+ },
+ "devDependencies": {
+ "@eslint/js": "^10.0.1",
+ "@types/react": "^19.2.17",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^6.0.3",
+ "eslint": "^10.6.0",
+ "eslint-plugin-react-hooks": "^7.1.1",
+ "eslint-plugin-react-refresh": "^0.5.3",
+ "globals": "^17.7.0",
+ "vite": "^8.1.1"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
+ "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
+ "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
+ "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.7"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
+ "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
+ "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz",
+ "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.2",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz",
+ "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
+ "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.23.5",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz",
+ "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^3.0.5",
+ "debug": "^4.3.1",
+ "minimatch": "^10.2.4"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz",
+ "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^1.2.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz",
+ "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz",
+ "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "eslint": "^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "eslint": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz",
+ "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz",
+ "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^1.2.1",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
+ "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/types": "^0.15.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.8",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz",
+ "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.2",
+ "@humanfs/types": "^0.15.0",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/types": {
+ "version": "0.15.0",
+ "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz",
+ "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
+ "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tybys/wasm-util": "^0.10.3"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.139.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz",
+ "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz",
+ "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz",
+ "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz",
+ "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz",
+ "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz",
+ "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz",
+ "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz",
+ "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz",
+ "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz",
+ "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz",
+ "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz",
+ "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz",
+ "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz",
+ "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "1.11.1",
+ "@emnapi/runtime": "1.11.1",
+ "@napi-rs/wasm-runtime": "^1.1.6"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz",
+ "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz",
+ "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.3",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
+ "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/esrecurse": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
+ "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.17",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz",
+ "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz",
+ "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rolldown/pluginutils": "^1.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
+ "babel-plugin-react-compiler": "^1.0.0",
+ "vite": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@rolldown/plugin-babel": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.17.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz",
+ "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
+ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.43",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz",
+ "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.7",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
+ "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.6",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz",
+ "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.10.42",
+ "caniuse-lite": "^1.0.30001803",
+ "electron-to-chromium": "^1.5.389",
+ "node-releases": "^2.0.51",
+ "update-browserslist-db": "^1.2.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001806",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz",
+ "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.393",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.393.tgz",
+ "integrity": "sha512-kiDJdIUawuEIcp9XoICKp1iTYDEbgguIPq526N1Q7jIQDeQ3CqoMx71025PI/7E48Ddtw2HuWsVjY7afEgNxmg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "10.7.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.7.0.tgz",
+ "integrity": "sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==",
+ "dev": true,
+ "license": "MIT",
+ "workspaces": [
+ "packages/*"
+ ],
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.2",
+ "@eslint/config-array": "^0.23.5",
+ "@eslint/config-helpers": "^0.6.0",
+ "@eslint/core": "^1.2.1",
+ "@eslint/plugin-kit": "^0.7.2",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.14.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^9.1.2",
+ "eslint-visitor-keys": "^5.0.1",
+ "espree": "^11.2.0",
+ "esquery": "^1.7.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "minimatch": "^10.2.4",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz",
+ "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.24.4",
+ "@babel/parser": "^7.24.4",
+ "hermes-parser": "^0.25.1",
+ "zod": "^3.25.0 || ^4.0.0",
+ "zod-validation-error": "^3.5.0 || ^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react-refresh": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.3.tgz",
+ "integrity": "sha512-5EMmLCV98Pi4o/f/3DP/v/tNqLHMIc9I8LKClNDWhZ9JTho89/kQcitCXQBMG7sAfVRK0Ie3T2EDOzp1YXYiVA==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "eslint": "^9 || ^10"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
+ "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@types/esrecurse": "^4.3.1",
+ "@types/estree": "^1.0.8",
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "11.2.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz",
+ "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.16.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^5.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "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"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "17.7.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz",
+ "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/hermes-estree": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
+ "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/hermes-parser": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
+ "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hermes-estree": "0.25.1"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.16",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
+ "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.51",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz",
+ "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.20",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.20.tgz",
+ "integrity": "sha512-lW616l85ucIQL+FocMmL7pQFPqBmwejrCMg+iPxyImlrANNJG9NHq/RkyCZopDhd8C3LA03PHRJDjkbGu8vvug==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.16",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.7",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
+ "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.7",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
+ "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.7"
+ }
+ },
+ "node_modules/react-router": {
+ "version": "7.18.1",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz",
+ "integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "^1.0.1",
+ "set-cookie-parser": "^2.6.0"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ },
+ "peerDependenciesMeta": {
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-router-dom": {
+ "version": "7.18.1",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.1.tgz",
+ "integrity": "sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.18.1"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz",
+ "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.139.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.1.5",
+ "@rolldown/binding-darwin-arm64": "1.1.5",
+ "@rolldown/binding-darwin-x64": "1.1.5",
+ "@rolldown/binding-freebsd-x64": "1.1.5",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.1.5",
+ "@rolldown/binding-linux-arm64-gnu": "1.1.5",
+ "@rolldown/binding-linux-arm64-musl": "1.1.5",
+ "@rolldown/binding-linux-ppc64-gnu": "1.1.5",
+ "@rolldown/binding-linux-s390x-gnu": "1.1.5",
+ "@rolldown/binding-linux-x64-gnu": "1.1.5",
+ "@rolldown/binding-linux-x64-musl": "1.1.5",
+ "@rolldown/binding-openharmony-arm64": "1.1.5",
+ "@rolldown/binding-wasm32-wasi": "1.1.5",
+ "@rolldown/binding-win32-arm64-msvc": "1.1.5",
+ "@rolldown/binding-win32-x64-msvc": "1.1.5"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/set-cookie-parser": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
+ "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
+ "license": "MIT"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "dev": true,
+ "license": "0BSD",
+ "optional": true
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "8.1.5",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz",
+ "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.32.0",
+ "picomatch": "^4.0.5",
+ "postcss": "^8.5.17",
+ "rolldown": "~1.1.5",
+ "tinyglobby": "^0.2.17"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.3.0",
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
+ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-validation-error": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
+ "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.0 || ^4.0.0"
+ }
+ }
+ }
+}
diff --git a/client/package.json b/client/package.json
new file mode 100644
index 000000000..ba655a020
--- /dev/null
+++ b/client/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "client",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "lint": "eslint .",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^19.2.7",
+ "react-dom": "^19.2.7",
+ "react-router-dom": "^7.18.1"
+ },
+ "devDependencies": {
+ "@eslint/js": "^10.0.1",
+ "@types/react": "^19.2.17",
+ "@types/react-dom": "^19.2.3",
+ "@vitejs/plugin-react": "^6.0.3",
+ "eslint": "^10.6.0",
+ "eslint-plugin-react-hooks": "^7.1.1",
+ "eslint-plugin-react-refresh": "^0.5.3",
+ "globals": "^17.7.0",
+ "vite": "^8.1.1"
+ }
+}
diff --git a/client/public/favicon.svg b/client/public/favicon.svg
new file mode 100644
index 000000000..6893eb132
--- /dev/null
+++ b/client/public/favicon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/public/icons.svg b/client/public/icons.svg
new file mode 100644
index 000000000..e9522193d
--- /dev/null
+++ b/client/public/icons.svg
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/App.css b/client/src/App.css
new file mode 100644
index 000000000..f90339d8f
--- /dev/null
+++ b/client/src/App.css
@@ -0,0 +1,184 @@
+.counter {
+ font-size: 16px;
+ padding: 5px 10px;
+ border-radius: 5px;
+ color: var(--accent);
+ background: var(--accent-bg);
+ border: 2px solid transparent;
+ transition: border-color 0.3s;
+ margin-bottom: 24px;
+
+ &:hover {
+ border-color: var(--accent-border);
+ }
+ &:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+ }
+}
+
+.hero {
+ position: relative;
+
+ .base,
+ .framework,
+ .vite {
+ inset-inline: 0;
+ margin: 0 auto;
+ }
+
+ .base {
+ width: 170px;
+ position: relative;
+ z-index: 0;
+ }
+
+ .framework,
+ .vite {
+ position: absolute;
+ }
+
+ .framework {
+ z-index: 1;
+ top: 34px;
+ height: 28px;
+ transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
+ scale(1.4);
+ }
+
+ .vite {
+ z-index: 0;
+ top: 107px;
+ height: 26px;
+ width: auto;
+ transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
+ scale(0.8);
+ }
+}
+
+#center {
+ display: flex;
+ flex-direction: column;
+ gap: 25px;
+ place-content: center;
+ place-items: center;
+ flex-grow: 1;
+
+ @media (max-width: 1024px) {
+ padding: 32px 20px 24px;
+ gap: 18px;
+ }
+}
+
+#next-steps {
+ display: flex;
+ border-top: 1px solid var(--border);
+ text-align: left;
+
+ & > div {
+ flex: 1 1 0;
+ padding: 32px;
+ @media (max-width: 1024px) {
+ padding: 24px 20px;
+ }
+ }
+
+ .icon {
+ margin-bottom: 16px;
+ width: 22px;
+ height: 22px;
+ }
+
+ @media (max-width: 1024px) {
+ flex-direction: column;
+ text-align: center;
+ }
+}
+
+#docs {
+ border-right: 1px solid var(--border);
+
+ @media (max-width: 1024px) {
+ border-right: none;
+ border-bottom: 1px solid var(--border);
+ }
+}
+
+#next-steps ul {
+ list-style: none;
+ padding: 0;
+ display: flex;
+ gap: 8px;
+ margin: 32px 0 0;
+
+ .logo {
+ height: 18px;
+ }
+
+ a {
+ color: var(--text-h);
+ font-size: 16px;
+ border-radius: 6px;
+ background: var(--social-bg);
+ display: flex;
+ padding: 6px 12px;
+ align-items: center;
+ gap: 8px;
+ text-decoration: none;
+ transition: box-shadow 0.3s;
+
+ &:hover {
+ box-shadow: var(--shadow);
+ }
+ .button-icon {
+ height: 18px;
+ width: 18px;
+ }
+ }
+
+ @media (max-width: 1024px) {
+ margin-top: 20px;
+ flex-wrap: wrap;
+ justify-content: center;
+
+ li {
+ flex: 1 1 calc(50% - 8px);
+ }
+
+ a {
+ width: 100%;
+ justify-content: center;
+ box-sizing: border-box;
+ }
+ }
+}
+
+#spacer {
+ height: 88px;
+ border-top: 1px solid var(--border);
+ @media (max-width: 1024px) {
+ height: 48px;
+ }
+}
+
+.ticks {
+ position: relative;
+ width: 100%;
+
+ &::before,
+ &::after {
+ content: '';
+ position: absolute;
+ top: -4.5px;
+ border: 5px solid transparent;
+ }
+
+ &::before {
+ left: 0;
+ border-left-color: var(--border);
+ }
+ &::after {
+ right: 0;
+ border-right-color: var(--border);
+ }
+}
diff --git a/client/src/App.jsx b/client/src/App.jsx
new file mode 100644
index 000000000..9a3895298
--- /dev/null
+++ b/client/src/App.jsx
@@ -0,0 +1,171 @@
+import { useState, useEffect } from 'react'
+import { useRoutes, Link, useLocation } from 'react-router-dom'
+import Nav from './components/Nav.jsx'
+import Home from './pages/Home.jsx'
+import Kitchen from './pages/Kitchen.jsx'
+import AddIngredient from './pages/AddIngredient.jsx'
+import EditIngredient from './pages/EditIngredient.jsx'
+import Login from './pages/Login.jsx'
+import Profile from './pages/Profile.jsx'
+import EditPreferences from './pages/EditPreferences.jsx'
+import Recipe from './pages/Recipe.jsx'
+import Calendar from './pages/Calendar.jsx'
+import Admin from './pages/Admin.jsx'
+
+function App() {
+ const [loggedIn, setLoggedIn] = useState(false)
+ const [user, setUser] = useState({})
+ const location = useLocation()
+ const isLoginPage = location.pathname === '/login'
+
+ const API_URL = "http://localhost:3000";
+
+ const getUser = async () => {
+ const response = await fetch(`${API_URL}/auth/login/success`, { credentials: 'include' } )
+
+ if (!response.ok) {
+ setUser(null)
+ setLoggedIn(false)
+ return
+ }
+
+ const json = await response.json()
+ setUser(json.user)
+ setLoggedIn(true)
+ }
+
+ useEffect(() => {
+ getUser()
+ }, []);
+
+ // let routes = useRoutes([
+ // {
+ // path:'/',
+ // element:
+ // },
+ // {
+ // path:'/home',
+ // element:
+ // },
+ // {
+ // path:'/kitchen',
+ // element:
+ // },
+ // {
+ // path:'/addIngredient',
+ // element:
+ // },
+ // {
+ // path:'/editIngredient/:id',
+ // element:
+ // },
+ // {
+ // path:'/login',
+ // element:
+ // }
+ // ])
+
+
+ let routes = useRoutes([
+ {
+ path: '/',
+ element:
+ },
+ {
+ path: '/home',
+ element:
+ },
+ {
+ path: '/kitchen',
+ element: user && user.id
+ ?
+ :
+ },
+ {
+ path: '/addIngredient',
+ element: user && user.id
+ ?
+ :
+ },
+ {
+ path: '/editIngredient/:id',
+ element: user && user.id
+ ?
+ :
+ },
+ {
+ path: '/profile',
+ element: user && user.id
+ ?
+ :
+ },
+ {
+ path: '/login',
+ element:
+ },
+ {
+ path:'/edit/preferences',
+ element: user && user.id
+ ?
+ :
+ },
+ {
+ path: '/recipe/:id',
+ element: user && user.id
+ ?
+ :
+ },
+ {
+ path: '/calendar',
+ element: user && user.id
+ ?
+ :
+ },
+ {
+ path: '/admin',
+ element: user && user.is_admin
+ ?
+ :
+ },
+ ])
+
+
+
+
+ return (
+
+ {loggedIn && !isLoginPage &&
}
+ {!loggedIn && !isLoginPage && (
+
+ EatRite
+ Log in
+
+ )}
+ {routes}
+
+ )
+}
+
+export default App
+
+const styles = {
+ guestBar: {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ border: 'solid black',
+ borderWidth: '2px',
+ height: '60px',
+ padding: '0 20px',
+ marginBottom: '5px',
+ fontSize: '20px',
+ },
+ guestLink: {
+ textDecoration: 'none',
+ color: 'black',
+ border: 'solid black',
+ borderWidth: '1px',
+ borderRadius: '5px',
+ padding: '8px 16px',
+ },
+}
diff --git a/client/src/assets/eggs.jpg b/client/src/assets/eggs.jpg
new file mode 100644
index 000000000..db923dc7f
Binary files /dev/null and b/client/src/assets/eggs.jpg differ
diff --git a/client/src/assets/fivestar.png b/client/src/assets/fivestar.png
new file mode 100644
index 000000000..4aa4a693b
Binary files /dev/null and b/client/src/assets/fivestar.png differ
diff --git a/client/src/assets/fourstar.png b/client/src/assets/fourstar.png
new file mode 100644
index 000000000..9509ecfd4
Binary files /dev/null and b/client/src/assets/fourstar.png differ
diff --git a/client/src/assets/githubimage.png b/client/src/assets/githubimage.png
new file mode 100644
index 000000000..d44393a07
Binary files /dev/null and b/client/src/assets/githubimage.png differ
diff --git a/client/src/assets/heart.png b/client/src/assets/heart.png
new file mode 100644
index 000000000..6f1320b71
Binary files /dev/null and b/client/src/assets/heart.png differ
diff --git a/client/src/assets/home-user.svg b/client/src/assets/home-user.svg
new file mode 100644
index 000000000..90fc564ae
--- /dev/null
+++ b/client/src/assets/home-user.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/src/assets/left-caret.png b/client/src/assets/left-caret.png
new file mode 100644
index 000000000..7a9196f74
Binary files /dev/null and b/client/src/assets/left-caret.png differ
diff --git a/client/src/assets/log-out.svg b/client/src/assets/log-out.svg
new file mode 100644
index 000000000..7181df4ec
--- /dev/null
+++ b/client/src/assets/log-out.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/src/assets/onestar.png b/client/src/assets/onestar.png
new file mode 100644
index 000000000..07a667abb
Binary files /dev/null and b/client/src/assets/onestar.png differ
diff --git a/client/src/assets/pizza_image.jpg b/client/src/assets/pizza_image.jpg
new file mode 100644
index 000000000..aaf70a5f8
Binary files /dev/null and b/client/src/assets/pizza_image.jpg differ
diff --git a/client/src/assets/right-caret.png b/client/src/assets/right-caret.png
new file mode 100644
index 000000000..fa7a07c0e
Binary files /dev/null and b/client/src/assets/right-caret.png differ
diff --git a/client/src/assets/threestar.png b/client/src/assets/threestar.png
new file mode 100644
index 000000000..1c04d7fa1
Binary files /dev/null and b/client/src/assets/threestar.png differ
diff --git a/client/src/assets/trash-icon.svg b/client/src/assets/trash-icon.svg
new file mode 100644
index 000000000..206cc3f3e
--- /dev/null
+++ b/client/src/assets/trash-icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/src/assets/twostar.png b/client/src/assets/twostar.png
new file mode 100644
index 000000000..d5ff6de2c
Binary files /dev/null and b/client/src/assets/twostar.png differ
diff --git a/client/src/components/Card.jsx b/client/src/components/Card.jsx
new file mode 100644
index 000000000..118e0753a
--- /dev/null
+++ b/client/src/components/Card.jsx
@@ -0,0 +1,101 @@
+import { Link, useNavigate } from 'react-router-dom'
+import heart from '../assets/heart.png'
+import favoritesApi from '../services/favoritesApi.js'
+
+function Card ({id, title, stars, image_url, avg_rating, loggedIn}) {
+ const navigate = useNavigate()
+
+ const createFavorite = (recipe_id) => {
+ if (!loggedIn) {
+ navigate('/login')
+ return
+ }
+ favoritesApi.createFavorite(recipe_id)
+ }
+
+ return (
+
+
+
+
+
{title}
+
+ {/*
*/}
+ {/*
{heart} */}
+
{avg_rating} stars
+
createFavorite(id)}
+ style={styles.button}
+ >
+
+
+
+ {/*
Recipe */}
+
+ )
+}
+
+export default Card;
+
+const styles = {
+ container: {
+ width: '240px',
+ height: '250px',
+ border: 'solid black',
+ borderRadius: '10px',
+ margin: '0 auto',
+ marginBottom: '30px',
+ padding:'10px',
+ display: 'flex',
+ flexDirection: 'column',
+ textDecoration: 'none',
+ },
+ title: {
+ fontSize: '20px',
+ textDecoration: 'none',
+ color: 'black',
+ },
+ img: {
+ display: 'block',
+ // width: '150px',
+ width: '200px',
+ height: '150px',
+ // width: '80%',
+ borderRadius: '10px',
+ margin: '0 auto',
+ marginTop: '5px',
+ marginBottom: '5px',
+ },
+ btn:{
+ display:'block',
+ width: '40%',
+ textAlign: 'center',
+ margin: '0 auto',
+ border: 'solid black',
+ borderWidth: '1px',
+ padding: '15px',
+ borderRadius: '10px',
+ fontSize: '20px',
+ fontWeight: '700',
+ color: 'black',
+ textDecoration: 'none',
+ },
+ starImage: {
+ width: '40%',
+ display: 'block',
+ },
+ interBox: {
+ // border: 'solid black',
+ display: 'flex',
+ justifyContent: "space-between",
+ marginTop: 'auto',
+ alignItems: 'center',
+ color: 'black',
+ fontSize: '20px',
+ },
+ heart: {
+ width: "20px",
+ height: "20px",
+ display: "block",
+ },
+}
diff --git a/client/src/components/Ingredient.jsx b/client/src/components/Ingredient.jsx
new file mode 100644
index 000000000..ee7a9a78d
--- /dev/null
+++ b/client/src/components/Ingredient.jsx
@@ -0,0 +1,36 @@
+import eggs from '../assets/eggs.jpg'
+
+
+const Ingredient = () => {
+ return (
+
+
+
+ Ingredient Name
+ Qty:
+ Category:
+
+
+
+ )
+}
+
+export default Ingredient
+
+const styles = {
+ container: {
+ display: 'flex',
+ border: 'solid black',
+ borderRadius: '10px',
+ alignItems: 'center',
+ padding: '10px',
+ gap: 20,
+ },
+ img: {
+ display: "block",
+ width: '100px',
+ height: '100px',
+ borderRadius: '10px',
+ margin: '0 auto',
+ }
+}
diff --git a/client/src/components/Nav.jsx b/client/src/components/Nav.jsx
new file mode 100644
index 000000000..c36b06164
--- /dev/null
+++ b/client/src/components/Nav.jsx
@@ -0,0 +1,133 @@
+import { Link } from 'react-router-dom';
+import { useState, useEffect, useRef } from 'react'
+import Menu from '../components/ProfileMenu.jsx'
+
+function Nav ({ user }) {
+ const [menuOpen, setMenuOpen] = useState(false)
+ const menuRef = useRef(null)
+
+ const toggleMenu = () => {
+ if (menuOpen == true) {
+ setMenuOpen(false)
+ } if (menuOpen == false) {
+ setMenuOpen(true)
+ }
+
+ console.log(`menuOpen: ${menuOpen}`)
+ }
+
+ useEffect(() => {
+ const closeMenu = (e) => {
+ if (!menuRef.current.contains(e.target)) {
+ setMenuOpen(false)
+ }
+ }
+
+ document.addEventListener("mousedown", closeMenu)
+
+ return () => {
+ document.removeEventListener("mousedown", closeMenu)
+ }
+
+ }, [])
+
+ return (
+
+
EatRite
+
+
Home
+
My Kitchen
+
Calendar
+ {user.is_admin &&
Admin }
+ {/*
Profile */}
+
+
+ toggleMenu()}
+ >
+
+
+ {menuOpen && (
+
+ )}
+
+
+
+
+ );
+}
+
+export default Nav;
+
+const styles = {
+ profileImg: {
+ // width: '50px'
+ },
+ nav: {
+ // backgroundImage: `url(${navpattern})`,
+ // backgroundColor: 'gray',
+ display: 'flex',
+ alignItems: 'center',
+ border: 'solid black',
+ borderWidth: '2px',
+ justifyContent: 'space-between',
+ height: '100px',
+ gap: '10px',
+ paddingRight: '10px',
+ marginBottom: '5px',
+ },
+ btns: {
+ display: 'flex',
+ alignItems: 'center',
+ },
+ profileContainer: {
+ position: 'relative',
+
+ },
+ profileBtn: {
+ // backgroundColor: "#27F561",
+ // backgroundImage: `url(${user.avatarurl})`,
+ cursor: 'pointer',
+ display: 'block',
+ width: '4px',
+ height: '4px',
+ backgroundSize: 'cover',
+ backgroundPosition: 'center',
+ fontSize: '15px',
+ color: 'black',
+ border: 'solid gray',
+ padding: '20px',
+ borderRadius: '50px',
+ borderWidth: '0.1px',
+ textDecoration: 'none',
+ margin: '10px',
+ },
+ btn: {
+ // backgroundColor: "#27F561",
+ display: 'block',
+ fontSize: '15px',
+ color: 'black',
+ border: 'solid black',
+ padding: '20px',
+ borderRadius: '5px',
+ borderWidth: '1px',
+ textDecoration: 'none',
+ margin: '10px',
+ },
+ title: {
+ fontSize: '40px',
+ fontWeight: '700',
+ color: 'black',
+ margin: '5px',
+ backgroundColor: 'white',
+ borderRadius: '5px',
+ padding: '10px',
+ }
+
+}
diff --git a/client/src/components/PrefItem.jsx b/client/src/components/PrefItem.jsx
new file mode 100644
index 000000000..40c987fef
--- /dev/null
+++ b/client/src/components/PrefItem.jsx
@@ -0,0 +1,70 @@
+import react from 'react'
+import preferenceApi from '../services/preferenceApi.js'
+
+
+const PrefItem = ({ id, preference, delRefresh, delModalRefresh }) => {
+
+ const deletePreference = (id) => {
+
+ preferenceApi.deletePreference(id)
+ delRefresh(id, 'pref')
+ delModalRefresh(id)
+ }
+
+ return (
+ <>
+
+
{preference}
+
deletePreference(id)}
+ >Delete
+
+
+ >
+ )
+}
+
+export default PrefItem
+
+const styles = {
+ prefItem: {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ fontSize: '20px',
+ margin: '10px',
+ width: '100%',
+ },
+ hr: {
+ border: 'none',
+ borderTop: '1px solid black',
+ width: '98%',
+ },
+ itemPref: {
+ textAlign: 'center',
+ border: 'solid black',
+ borderRadius: '15px',
+ borderWidth: '1px',
+ width: '125px',
+ height: '30px',
+ backgroundColor: '#dcdcdc',
+ },
+ itemTxt: {
+ display: 'block',
+ },
+ btn: {
+ textDecoration: 'none',
+ color: 'black',
+ cursor: 'pointer',
+ height: '40px',
+ width: '100px',
+ border: 'solid black',
+ borderRadius: '5px',
+ borderWidth:'1px',
+ padding: '10px',
+ borderColor: 'red',
+ }
+}
+
+
diff --git a/client/src/components/ProfileIngrCard.jsx b/client/src/components/ProfileIngrCard.jsx
new file mode 100644
index 000000000..8613764b1
--- /dev/null
+++ b/client/src/components/ProfileIngrCard.jsx
@@ -0,0 +1,99 @@
+import { Link } from 'react-router-dom'
+import heart from '../assets/heart.png'
+import favoritesApi from '../services/favoritesApi.js'
+import trash from '../assets/trash-icon.svg'
+
+function Card ({id, title, stars, image_url, avg_rating, refresh}) {
+
+ const deleteFavorite = (recipe_id) => {
+
+ favoritesApi.deleteFavorite(recipe_id)
+ refresh(recipe_id, 'fav')
+ }
+
+ return (
+
+
+
+
+
{title}
+
+ {/*
*/}
+ {/*
{heart} */}
+
{avg_rating} stars
+
deleteFavorite(id)}
+ style={styles.button}
+ >
+
+
+
+ {/*
Recipe */}
+
+ )
+}
+
+export default Card;
+
+const styles = {
+ container: {
+ width: '240px',
+ height: '250px',
+ border: 'solid black',
+ borderRadius: '10px',
+ margin: '0 auto',
+ marginBottom: '30px',
+ padding:'10px',
+ display: 'flex',
+ flexDirection: 'column',
+ textDecoration: 'none',
+ },
+ title: {
+ fontSize: '20px',
+ textDecoration: 'none',
+ color: 'black',
+ },
+ img: {
+ display: 'block',
+ // width: '150px',
+ width: '200px',
+ height: '150px',
+ // width: '80%',
+ borderRadius: '10px',
+ margin: '0 auto',
+ marginTop: '5px',
+ marginBottom: '5px',
+ },
+ btn:{
+ display:'block',
+ width: '40%',
+ textAlign: 'center',
+ margin: '0 auto',
+ border: 'solid black',
+ borderWidth: '1px',
+ padding: '15px',
+ borderRadius: '10px',
+ fontSize: '20px',
+ fontWeight: '700',
+ color: 'black',
+ textDecoration: 'none',
+ },
+ starImage: {
+ width: '40%',
+ display: 'block',
+ },
+ interBox: {
+ // border: 'solid black',
+ display: 'flex',
+ justifyContent: "space-between",
+ marginTop: 'auto',
+ alignItems: 'center',
+ color: 'black',
+ fontSize: '20px',
+ },
+ heart: {
+ width: "20px",
+ height: "20px",
+ display: "block",
+ },
+}
diff --git a/client/src/components/ProfileMenu.jsx b/client/src/components/ProfileMenu.jsx
new file mode 100644
index 000000000..27fc670a1
--- /dev/null
+++ b/client/src/components/ProfileMenu.jsx
@@ -0,0 +1,105 @@
+import { useState } from 'react'
+import { Link } from 'react-router-dom';
+import logoutimg from '../assets/log-out.svg'
+import userimg from '../assets/home-user.svg'
+import auth from '../services/auth.js'
+
+const ProfileMenu = ({ user }) => {
+
+ return (
+
+
+
+
{user.username}
+
+
+
+
+
Profile
+
+
+
+ Logout
+
+
+ )
+}
+
+export default ProfileMenu
+
+const styles = {
+ profileInfo: {
+ display: 'flex',
+ alignItems: 'center',
+ },
+ menuContainer: {
+ position: 'absolute',
+ top: '55px',
+ right: '12px',
+ zIndex: '1000',
+ border: 'solid black',
+ borderRadius: '10px',
+ borderWidth: '1px',
+ backgroundColor: 'white',
+ height: '230px',
+ width: '150px',
+ },
+ profileImg: {
+ // backgroundColor: "#27F561",
+ // backgroundImage: `url(${user.avatarurl})`,
+ // cursor: 'pointer',
+ display: 'block',
+ width: '4px',
+ height: '4px',
+ backgroundSize: 'cover',
+ backgroundPosition: 'center',
+ fontSize: '15px',
+ color: 'black',
+ border: 'solid gray',
+ padding: '20px',
+ borderRadius: '50px',
+ borderWidth: '0.1px',
+ textDecoration: 'none',
+ margin: '10px',
+ },
+ btn: {
+ // backgroundColor: "#27F561",
+ // display: 'block',
+ display: 'flex',
+ alignItems: 'center',
+ gap: 5,
+ fontSize: '15px',
+ color: 'black',
+ border: 'solid black',
+ padding: '20px',
+ borderRadius: '5px',
+ borderWidth: '1px',
+ textDecoration: 'none',
+ margin: '10px',
+ },
+ logoutBtn: {
+ // backgroundColor: "#27F561",
+ // display: 'block',
+ cursor: 'pointer',
+ display: 'flex',
+ alignItems: 'center',
+ gap: 5,
+ fontSize: '15px',
+ color: 'black',
+ border: 'solid black',
+ padding: '20px',
+ width: '130px',
+ borderRadius: '5px',
+ borderWidth: '1px',
+ textDecoration: 'none',
+ margin: '10px',
+ },
+}
diff --git a/client/src/components/SearchBar.jsx b/client/src/components/SearchBar.jsx
new file mode 100644
index 000000000..37b7fb20f
--- /dev/null
+++ b/client/src/components/SearchBar.jsx
@@ -0,0 +1,79 @@
+import { useState } from 'react'
+
+const styles = {
+ search: {
+ padding: "8px 12px",
+ width: "250px",
+ fontSize: "16px",
+ border: "1px solid #ccc",
+ borderRadius: "8px",
+ outline: "none",
+ },
+};
+
+function SearchBar(/* { items, onSelect } */) {
+ const [query, setQuery] = useState("");
+
+ // const filteredItems = items.filter((item) =>
+ // item.name.toLowerCase().includes(query.toLowerCase())
+ // );
+
+ return (
+ setQuery(e.target.value)}
+ placeholder="Search..."
+ style={styles.search}
+ />
+ );
+}
+
+export default SearchBar;
+
+//
+// import { useState } from "react";
+//
+// const styles = {
+// search: {
+// padding: "8px 12px",
+// width: "250px",
+// fontSize: "16px",
+// border: "1px solid #ccc",
+// borderRadius: "8px",
+// outline: "none",
+// },
+// };
+//
+// function SearchBar({ items, onSelect }) {
+// const [query, setQuery] = useState("");
+//
+// const filtereditems = items.filter((item) =>
+// item.name.toLowerCase().includes(query.toLowerCase())
+// );
+//
+// return (
+//
+//
setQuery(e.target.value)}
+// placeholder="Search..."
+// style={styles.search}
+// />
+//
+//
+// {filteredItems.map((item) => (
+//
onSelect(item)}
+// >
+// {item.name}
+//
+// ))}
+//
+//
+// );
+// }
+//
+// export default SearchBar;
diff --git a/client/src/index.css b/client/src/index.css
new file mode 100644
index 000000000..160eb3136
--- /dev/null
+++ b/client/src/index.css
@@ -0,0 +1,111 @@
+/* :root { */
+/* --text: #6b6375; */
+/* --text-h: #08060d; */
+/* --bg: #fff; */
+/* --border: #e5e4e7; */
+/* --code-bg: #f4f3ec; */
+/* --accent: #aa3bff; */
+/* --accent-bg: rgba(170, 59, 255, 0.1); */
+/* --accent-border: rgba(170, 59, 255, 0.5); */
+/* --social-bg: rgba(244, 243, 236, 0.5); */
+/* --shadow: */
+/* rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px; */
+/**/
+/* --sans: system-ui, 'Segoe UI', Roboto, sans-serif; */
+/* --heading: system-ui, 'Segoe UI', Roboto, sans-serif; */
+/* --mono: ui-monospace, Consolas, monospace; */
+/**/
+/* font: 18px/145% var(--sans); */
+/* letter-spacing: 0.18px; */
+/* color-scheme: light dark; */
+/* color: var(--text); */
+/* background: var(--bg); */
+/* font-synthesis: none; */
+/* text-rendering: optimizeLegibility; */
+/* -webkit-font-smoothing: antialiased; */
+/* -moz-osx-font-smoothing: grayscale; */
+/**/
+/* @media (max-width: 1024px) { */
+/* font-size: 16px; */
+/* } */
+/* } */
+/**/
+/* @media (prefers-color-scheme: dark) { */
+/* :root { */
+/* --text: #9ca3af; */
+/* --text-h: #f3f4f6; */
+/* --bg: #16171d; */
+/* --border: #2e303a; */
+/* --code-bg: #1f2028; */
+/* --accent: #c084fc; */
+/* --accent-bg: rgba(192, 132, 252, 0.15); */
+/* --accent-border: rgba(192, 132, 252, 0.5); */
+/* --social-bg: rgba(47, 48, 58, 0.5); */
+/* --shadow: */
+/* rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px; */
+/* } */
+/**/
+/* #social .button-icon { */
+/* filter: invert(1) brightness(2); */
+/* } */
+/* } */
+/**/
+/* body { */
+/* margin: 0; */
+/* } */
+/**/
+/* #root { */
+/* width: 1126px; */
+/* max-width: 100%; */
+/* margin: 0 auto; */
+/* text-align: center; */
+/* border-inline: 1px solid var(--border); */
+/* min-height: 100svh; */
+/* display: flex; */
+/* flex-direction: column; */
+/* box-sizing: border-box; */
+/* } */
+/**/
+/* h1, */
+/* h2 { */
+/* font-family: var(--heading); */
+/* font-weight: 500; */
+/* color: var(--text-h); */
+/* } */
+/**/
+/* h1 { */
+/* font-size: 56px; */
+/* letter-spacing: -1.68px; */
+/* margin: 32px 0; */
+/* @media (max-width: 1024px) { */
+/* font-size: 36px; */
+/* margin: 20px 0; */
+/* } */
+/* } */
+/* h2 { */
+/* font-size: 24px; */
+/* line-height: 118%; */
+/* letter-spacing: -0.24px; */
+/* margin: 0 0 8px; */
+/* @media (max-width: 1024px) { */
+/* font-size: 20px; */
+/* } */
+/* } */
+/* p { */
+/* margin: 0; */
+/* } */
+/**/
+/* code, */
+/* .counter { */
+/* font-family: var(--mono); */
+/* display: inline-flex; */
+/* border-radius: 4px; */
+/* color: var(--text-h); */
+/* } */
+/**/
+/* code { */
+/* font-size: 15px; */
+/* line-height: 135%; */
+/* padding: 4px 8px; */
+/* background: var(--code-bg); */
+/* } */
diff --git a/client/src/main.jsx b/client/src/main.jsx
new file mode 100644
index 000000000..67b6b2c76
--- /dev/null
+++ b/client/src/main.jsx
@@ -0,0 +1,15 @@
+import { StrictMode } from 'react'
+import { createRoot } from 'react-dom/client'
+import { BrowserRouter } from 'react-router-dom'
+import './index.css'
+import App from './App.jsx'
+// import Login from './pages/Login.jsx'
+
+createRoot(document.getElementById('root')).render(
+
+
+
+ {/* */}
+
+ ,
+)
diff --git a/client/src/pages/AddIngredient.jsx b/client/src/pages/AddIngredient.jsx
new file mode 100644
index 000000000..cba87d5d2
--- /dev/null
+++ b/client/src/pages/AddIngredient.jsx
@@ -0,0 +1,229 @@
+import { useState } from 'react'
+import { useNavigate } from 'react-router-dom'
+import api from '../services/api.jsx'
+
+const AddIngredient = () => {
+ const [ingredient, setIngredient] = useState({
+ name: '',
+ category: '',
+ calories: '',
+ dietary_tags: '',
+ quantity: '',
+ unit: ''
+ });
+
+ const categories = [
+ 'category',
+ 'pantry',
+ 'fridge',
+ 'freezer',
+ 'cabinet',
+ 'counter',
+ 'drawer',
+ 'shelf',
+ 'spice rack'
+ ]
+
+ const handleChange = (event) => {
+ const {name, value} = event.target
+ setIngredient((prev) => {
+ return {
+ ...prev,
+ [name]:value
+ }
+ })
+ }
+
+ const navigate = useNavigate()
+
+ const handleCancel = (event) => {
+ navigate('/kitchen')
+ }
+
+ const addIngredient = (event) => {
+ event.preventDefault()
+
+ api.addToKitchen(ingredient)
+ }
+
+ return (
+
+ )
+}
+
+export default AddIngredient
+
+const styles = {
+ title: {
+ display: 'block',
+ fontSize: '30',
+ marginBottom: '1px',
+ // textAlign: 'center',
+ // margin: '0 auto',
+ },
+ formContainer: {
+ border: 'solid black',
+ backgroundColor: '#fafafa',
+ borderRadius: '15px',
+ display: 'flex',
+ padding: '20px',
+ // justifyContent: 'center',
+ fontWeight: 'bold',
+ width: '700px',
+ // margin: '0 auto',
+ marginTop: '0px',
+ },
+ input: {
+ display: 'block',
+ // border: 'solid black',
+ width: '150px',
+ height: '30px',
+ borderRadius: '5px',
+
+ },
+ textInput: {
+ display: 'block',
+ // border: 'solid black',
+ width: '600px',
+ height: '250px',
+ borderRadius: '5px',
+
+ },
+ btnBox: {
+ display: 'flex',
+ gap:'10px',
+ },
+ btn1: {
+ cursor: 'pointer',
+ display: 'flex',
+ justifyContent:'center',
+ alignItems:'center',
+ width: '150px',
+ height: '50px',
+ fontSize: '15px',
+ fontWeight: 'bold',
+ backgroundColor: '#333333',
+ color: 'white',
+ border: 'solid black',
+ padding: '20px',
+ borderRadius: '5px',
+ borderWidth: '1px',
+ textDecoration: 'none',
+ // display: 'block',
+ marginTop: '20px',
+ marginRight: '20px',
+ },
+ btn2: {
+ cursor: 'pointer',
+ display: 'flex',
+ justifyContent:'center',
+ alignItems:'center',
+ width: '150px',
+ height: '50px',
+ fontSize: '15px',
+ fontWeight: 'bold',
+ color: 'black',
+ border: 'solid black',
+ padding: '20px',
+ borderRadius: '5px',
+ borderWidth: '1px',
+ textDecoration: 'none',
+ // display: 'block',
+ marginTop: '20px',
+ marginRight: '20px',
+ },
+}
diff --git a/client/src/pages/Admin.jsx b/client/src/pages/Admin.jsx
new file mode 100644
index 000000000..253784513
--- /dev/null
+++ b/client/src/pages/Admin.jsx
@@ -0,0 +1,113 @@
+import { useState, useEffect } from 'react'
+import api from '../services/api.jsx'
+
+const Admin = () => {
+ const [recipes, setRecipes] = useState([])
+ const [form, setForm] = useState({ title: '', description: '', instructions: '', image_url: '' })
+
+ const refreshRecipes = async () => {
+ const data = await api.getRecipes()
+ setRecipes(data || [])
+ }
+
+ useEffect(() => {
+ refreshRecipes()
+ }, [])
+
+ const handleChange = (event) => {
+ const { name, value } = event.target
+ setForm((prev) => ({ ...prev, [name]: value }))
+ }
+
+ const handleCreate = async (event) => {
+ event.preventDefault()
+ await api.createRecipe(form)
+ setForm({ title: '', description: '', instructions: '', image_url: '' })
+ refreshRecipes()
+ }
+
+ const handleDelete = async (id) => {
+ await api.deleteRecipe(id)
+ setRecipes((prev) => prev.filter((r) => r.id !== id))
+ }
+
+ return (
+
+ )
+}
+
+export default Admin
+
+const styles = {
+ title: {
+ marginLeft: '10px',
+ },
+ form: {
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '10px',
+ padding: '10px',
+ maxWidth: '400px',
+ },
+ input: {
+ height: '35px',
+ borderRadius: '5px',
+ },
+ textarea: {
+ height: '100px',
+ borderRadius: '5px',
+ },
+ btn: {
+ cursor: 'pointer',
+ height: '35px',
+ border: 'solid black',
+ borderWidth: '1px',
+ borderRadius: '5px',
+ },
+ table: {
+ width: '100%',
+ borderCollapse: 'collapse',
+ marginTop: '20px',
+ },
+ header: {
+ backgroundColor: '#333',
+ color: 'white',
+ padding: '12px',
+ textAlign: 'left',
+ },
+ cell: {
+ border: '1px solid #ddd',
+ padding: '12px',
+ },
+}
diff --git a/client/src/pages/Calendar.jsx b/client/src/pages/Calendar.jsx
new file mode 100644
index 000000000..16f367a28
--- /dev/null
+++ b/client/src/pages/Calendar.jsx
@@ -0,0 +1,276 @@
+import { useState, useEffect } from 'react'
+import api from '../services/api.jsx'
+import scheduledMealsApi from '../services/scheduledMealsApi.js'
+import leftC from '../assets/left-caret.png'
+import rightC from '../assets/right-caret.png'
+
+const mealTypes = ['breakfast', 'lunch', 'dinner', 'snack']
+
+const toDateStr = (date) => date.toISOString().slice(0, 10)
+
+const Calendar = () => {
+ const [selectedDate, setSelectedDate] = useState(new Date())
+ const [recipes, setRecipes] = useState([])
+ const [meals, setMeals] = useState([])
+ const [showForm, setShowForm] = useState(false)
+ const [form, setForm] = useState({ recipe_id: '', meal_type: mealTypes[0] })
+
+ const refreshMeals = async () => {
+ const mealData = await scheduledMealsApi.getScheduledMeals()
+ setMeals(Array.isArray(mealData) ? mealData : [])
+ }
+
+ useEffect(() => {
+ const load = async () => {
+ const recipeData = await api.getRecipes()
+ setRecipes(recipeData || [])
+ refreshMeals()
+ }
+ load()
+ }, [])
+
+ const shiftDay = (amount) => {
+ setSelectedDate((prev) => {
+ const next = new Date(prev)
+ next.setDate(next.getDate() + amount)
+ return next
+ })
+ }
+
+ const year = selectedDate.getFullYear()
+ const month = selectedDate.getMonth()
+ const daysInMonth = new Date(year, month + 1, 0).getDate()
+ const firstWeekday = (new Date(year, month, 1).getDay() + 6) % 7 // 0 = Monday
+
+ const mealsByDate = meals.reduce((acc, m) => {
+ const key = m.date.slice(0, 10)
+ acc[key] = acc[key] || []
+ acc[key].push(m)
+ return acc
+ }, {})
+
+ const selectedDateStr = toDateStr(selectedDate)
+ const selectedDayMeals = mealsByDate[selectedDateStr] || []
+
+ const handleChange = (event) => {
+ const { name, value } = event.target
+ setForm((prev) => ({ ...prev, [name]: value }))
+ }
+
+ const handleSchedule = async (event) => {
+ event.preventDefault()
+ if (!form.recipe_id) return
+ await scheduledMealsApi.createScheduledMeal({ ...form, date: selectedDateStr })
+ setShowForm(false)
+ setForm({ recipe_id: '', meal_type: mealTypes[0] })
+ refreshMeals()
+ }
+
+ const handleDelete = async (id) => {
+ await scheduledMealsApi.deleteScheduledMeal(id)
+ setMeals((prev) => prev.filter((m) => m.id !== id))
+ }
+
+ const handleMealDay = (cellDate, recipe_id) => {
+ setSelectedDate(cellDate)
+ if (recipe_id) {
+ window.location.href = `/recipe/${recipe_id}`
+ console.log(recipe_id)
+ }
+ }
+
+ return (
+
+
+
+
Meal Calendar
+
+
+
shiftDay(-1)}
+ src={leftC}
+ style={styles.caret}
+ />
+
{selectedDate.toLocaleString('default', { month: 'short' })} {selectedDate.getDate()}
+
shiftDay(1)}
+ src={rightC}
+ style={styles.caret} />
+
+
setShowForm((prev) => !prev)}>+Schedule Meal
+
+
+
+
+ {showForm && (
+
+
+ Select a recipe
+ {recipes.map((r) => (
+ {r.title}
+ ))}
+
+
+ {mealTypes.map((t) => (
+ {t}
+ ))}
+
+ Save
+
+ )}
+
+
+
Monday
+
Tuesday
+
Wednesday
+
Thursday
+
Friday
+
Saturday
+
Sunday
+
+ {Array.from({ length: firstWeekday }, (_, i) => (
+
+ ))}
+
+ {Array.from({ length: daysInMonth }, (_, i) => {
+ const dayNum = i + 1
+ const cellDate = new Date(year, month, dayNum)
+ const cellDateStr = toDateStr(cellDate)
+ const isSelected = cellDateStr === selectedDateStr
+ const dayMeals = mealsByDate[cellDateStr] || []
+ return (
+
handleMealDay(cellDate, dayMeals[0]?.recipe_id)}
+ >
+ {dayNum}
+ {dayMeals.length > 0 && {dayMeals.length} meal{dayMeals.length > 1 ? 's' : ''} }
+
+ )
+ })}
+
+
+
+
{selectedDate.toDateString()}
+
+ {selectedDayMeals.map((m) => (
+
+ {m.meal_type} — {m.title}
+ handleDelete(m.id)}>Remove
+
+ ))}
+
+
+
+
+ )
+}
+
+export default Calendar
+
+const styles = {
+ body: {
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ title: {
+ marginLeft: '30px',
+ },
+ form: {
+ display: 'flex',
+ gap: '10px',
+ alignItems: 'center',
+ marginBottom: '10px',
+ },
+ input: {
+ height: '35px',
+ borderRadius: '5px',
+ },
+ scheduleBtn: {
+ cursor: 'pointer',
+ marginRight: '30px',
+ fontSize: '15px',
+ backgroundColor: '#333333',
+ color: 'white',
+ border: 'solid black',
+ width: '200px',
+ height: '45px',
+ borderRadius: '5px',
+ borderWidth: '1px',
+ textDecoration: 'none',
+ margin: '10px',
+ },
+ topBox: {
+ display: 'flex',
+ justifyContent: 'space-between',
+ width: '100%',
+ },
+ grid: {
+ display: 'grid',
+ gridTemplateColumns: 'repeat(7,150px)',
+ marginTop: '20px',
+ },
+ dow: {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ border: '1px solid black',
+ borderRadius: '10px',
+ height: '30px',
+ },
+ day: {
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ justifyContent: 'center',
+ border: '1px solid black',
+ borderRadius: '10px',
+ height: '100px',
+ cursor: 'pointer',
+ },
+ daySelected: {
+ backgroundColor: '#dcdcdc',
+ },
+ mealDot: {
+ fontSize: '11px',
+ },
+ dowState: {
+ fontSize: '20px',
+ paddingLeft: '10px',
+ },
+ caret: {
+ height: '20px',
+ cursor: 'pointer',
+ },
+ setDateBox: {
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ gap: 10,
+ },
+ selectedBox: {
+ width: '100%',
+ maxWidth: '800px',
+ marginTop: '20px',
+ },
+ list: {
+ listStyle: 'none',
+ padding: 0,
+ },
+ item: {
+ display: 'flex',
+ justifyContent: 'space-between',
+ border: 'solid black',
+ borderWidth: '1px',
+ borderRadius: '10px',
+ padding: '10px',
+ marginBottom: '10px',
+ maxWidth: '500px',
+ },
+ deleteBtn: {
+ cursor: 'pointer',
+ },
+}
diff --git a/client/src/pages/EditIngredient.jsx b/client/src/pages/EditIngredient.jsx
new file mode 100644
index 000000000..f6c5ce5ac
--- /dev/null
+++ b/client/src/pages/EditIngredient.jsx
@@ -0,0 +1,220 @@
+import { useState, useEffect } from 'react'
+import { useNavigate, useParams } from 'react-router-dom'
+import api from '../services/api.jsx'
+
+const EditIngredient = () => {
+ const [ingredient, setIngredient] = useState({
+ // name: '',
+ // category: '',
+ // calories: '',
+ // dietary_tags: ''
+ });
+
+ const categories = [
+ 'category',
+ 'pantry',
+ 'fridge',
+ 'freezer',
+ 'cabinet',
+ 'counter',
+ 'drawer',
+ 'shelf',
+ 'spice rack'
+ ]
+
+ const { id } = useParams();
+
+ const handleChange = (event) => {
+ const {name, value} = event.target
+ setIngredient((prev) => {
+ return {
+ ...prev,
+ [name]:value
+ }
+ })
+ }
+
+
+ const navigate = useNavigate()
+
+ const handleCancel = (event) => {
+ navigate('/kitchen')
+ }
+
+ useEffect(() => {
+ const loadIngredient = async () => {
+ const data = await api.getIngredient(id);
+ setIngredient(data)
+ }
+
+ loadIngredient()
+ }, [id])
+
+ const EditIngredient = (event) => {
+ event.preventDefault()
+
+ const options = {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(ingredient)
+ }
+ api.updateIngredient(id, options)
+ }
+
+ return (
+
+ )
+}
+
+export default EditIngredient
+
+const styles = {
+ title: {
+ display: 'block',
+ fontSize: '30',
+ marginBottom: '1px',
+ // textAlign: 'center',
+ // margin: '0 auto',
+ },
+ formContainer: {
+ border: 'solid black',
+ backgroundColor: '#fafafa',
+ borderRadius: '15px',
+ display: 'flex',
+ padding: '20px',
+ // justifyContent: 'center',
+ fontWeight: 'bold',
+ width: '700px',
+ // margin: '0 auto',
+ marginTop: '0px',
+ },
+ input: {
+ display: 'block',
+ // border: 'solid black',
+ width: '150px',
+ height: '30px',
+ borderRadius: '5px',
+
+ },
+ textInput: {
+ display: 'block',
+ // border: 'solid black',
+ width: '600px',
+ height: '250px',
+ borderRadius: '5px',
+
+ },
+ btnBox: {
+ display: 'flex',
+ gap:'10px',
+ },
+ btn1: {
+ cursor: 'pointer',
+ display: 'flex',
+ justifyContent:'center',
+ alignItems:'center',
+ width: '150px',
+ height: '50px',
+ fontSize: '15px',
+ fontWeight: 'bold',
+ backgroundColor: '#333333',
+ color: 'white',
+ border: 'solid black',
+ padding: '20px',
+ borderRadius: '5px',
+ borderWidth: '1px',
+ textDecoration: 'none',
+ // display: 'block',
+ marginTop: '20px',
+ marginRight: '20px',
+ },
+ btn2: {
+ cursor: 'pointer',
+ display: 'flex',
+ justifyContent:'center',
+ alignItems:'center',
+ width: '150px',
+ height: '50px',
+ fontSize: '15px',
+ fontWeight: 'bold',
+ color: 'black',
+ border: 'solid black',
+ padding: '20px',
+ borderRadius: '5px',
+ borderWidth: '1px',
+ textDecoration: 'none',
+ // display: 'block',
+ marginTop: '20px',
+ marginRight: '20px',
+ },
+}
diff --git a/client/src/pages/EditPreferences.jsx b/client/src/pages/EditPreferences.jsx
new file mode 100644
index 000000000..a5a3fa516
--- /dev/null
+++ b/client/src/pages/EditPreferences.jsx
@@ -0,0 +1,158 @@
+import { useState, useEffect } from 'react'
+// import userApi from '../services/userApi.jsx'
+import preferenceApi from '../services/preferenceApi.js'
+import PrefItem from '../components/PrefItem.jsx'
+
+const EditPreferences = ({ toggle, delRefresh, inRefresh}) => {
+ const [preferences, setPreferences] = useState([])
+ const [newPref, setNewPref] = useState({ preference: ''})
+
+ const handleChange = (event) => {
+ const { name, value } = event.target
+
+ setNewPref( (prev) => {
+ return {
+ ...prev,
+ [name]:value,
+ }
+ })
+ }
+
+ const handleModalDeleteRefresh = (id) => {
+ setPreferences(prev =>
+ prev.filter(preference => preference.id !== id)
+ )
+ console.log('delete modal refreshing')
+ }
+
+ useEffect(() => {
+ const fetchPreference = async () => {
+ const data = await preferenceApi.getPreferences()
+ setPreferences(data)
+ }
+ fetchPreference()
+ }, [])
+
+ const createPreference = (event) => {
+ event.preventDefault()
+
+ preferenceApi.createPreference(newPref)
+ setPreferences(prev => [
+ ...prev,
+ newPref
+ ])
+ inRefresh(newPref)
+ }
+
+ return (
+
+
Preferences
+
+ {preferences.map((p) => (
+
+ ))}
+
+
+
+ )
+}
+
+export default EditPreferences
+
+const styles = {
+ title: {
+ // marginLeft: '20px',
+ textAlign: 'center',
+ },
+ addBox: {
+ display: 'flex',
+ alignItems: 'center',
+ gap: 10,
+ // border: 'solid black',
+ },
+ input: {
+ display: 'block',
+ width: '100px',
+ height: '35px',
+ },
+ editPicker: {
+ // margin: '0 auto',
+ position: 'absolute',
+ // padding: '30px',
+ width: '80%',
+ top: '5%',
+ left: '10%',
+ border: 'solid black',
+ borderRadius: '15px',
+ borderWidth: '1px',
+ backgroundColor: '#f3f3f3',
+ },
+ bottomBox: {
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ width: '100%',
+ padding: '20px',
+ boxSizing: 'border-box',
+ // border: 'solid black',
+ // paddingLeft: '10px',
+ // paddingRight: '10px',
+ },
+ prefBox: {
+ display:'flex',
+ flexWrap: 'wrap',
+ margin: '10px',
+ zIndex: '100',
+ },
+ addBtn: {
+ fontSize:'15px',
+ textDecoration: 'none',
+ color: 'black',
+ cursor: 'pointer',
+ height: '35px',
+ width: '55px',
+ alignText: 'center',
+ border: 'solid black',
+ borderRadius: '5px',
+ borderWidth:'1px',
+ padding: '10px',
+ },
+ btn: {
+ fontSize:'15px',
+ // marginRight: '25px',
+ // marginTop: '25px',
+ textDecoration: 'none',
+ color: 'black',
+ cursor: 'pointer',
+ height: '35px',
+ width: '55px',
+ alignText: 'center',
+ border: 'solid black',
+ borderRadius: '5px',
+ borderWidth:'1px',
+ padding: '10px',
+
+ },
+}
diff --git a/client/src/pages/Home.jsx b/client/src/pages/Home.jsx
new file mode 100644
index 000000000..2481a2780
--- /dev/null
+++ b/client/src/pages/Home.jsx
@@ -0,0 +1,137 @@
+import React from 'react'
+import { useEffect, useState } from 'react'
+import Card from '../components/Card.jsx'
+import Search from '../components/SearchBar.jsx'
+import pizzaimage from "../assets/pizza_image.jpg"
+import fourstar from "../assets/fourstar.png"
+import api from "../services/api.jsx"
+
+const Home = ({ user }) => {
+ const [recipes, setRecipes] = useState([])
+ const [sortBy, setSortBy] = useState('none')
+
+ useEffect(() => {
+ const loadRecipes = async () => {
+ const data = await api.getRecipes()
+ console.log("API DATA:", data)
+ setRecipes(data)
+ }
+ loadRecipes()
+ }, [])
+
+ const sortedRecipes = [...recipes].sort((a, b) => {
+ if (sortBy === 'rating') {
+ return (b.avg_rating ?? 0) - (a.avg_rating ?? 0)
+ }
+ if (sortBy === 'newest') {
+ return b.id - a.id
+ }
+ if (sortBy === 'oldest') {
+ return a.id - b.id
+ }
+ return 0
+ })
+
+ return (
+
+
+
Recipe Results h1>
+
+
+
+
+
+ Sort by Rating:
+ setSortBy(e.target.value || 'none')}
+ >
+ --
+ Highest rated
+
+
+
+ Sort by Date:
+ setSortBy(e.target.value || 'none')}
+ >
+ --
+ Newest
+ Oldest
+
+
+
+
+
+
+
+
+ {sortedRecipes.map((r) => (
+
+ ))}
+
+ {/* */}
+
+
+
+ )
+}
+
+export default Home;
+
+const styles = {
+ titleBox: {
+ textAlign: 'left',
+ paddingLeft: '5px',
+ },
+ filterTitle: {
+ fontSize: '20px',
+ // height: '1px',
+ },
+ filterBox: {
+ border: 'solid black',
+ marginBottom: '20px',
+ padding: '10px',
+ display: 'flex',
+ justifyContent: 'space-between',
+ },
+ selectBox: {
+ fontSize: '20px',
+ padding: '10px',
+ },
+ dropdownBox: {
+ display: 'flex',
+ gap: 20,
+ },
+ dropdown: {
+ display: "flex",
+ // flexDirection: "column",
+ alignItems: "center",
+ gap: "8px",
+ },
+ label: {
+ fontSize: "18px",
+ fontWeight: "bold",
+ },
+ select: {
+ padding: "8px 12px",
+ borderRadius: "8px",
+ border: "1px solid #ccc",
+ fontSize: "16px",
+ },
+ feed: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ padding: '10px',
+ },
+}
diff --git a/client/src/pages/Kitchen.jsx b/client/src/pages/Kitchen.jsx
new file mode 100644
index 000000000..bebcef78a
--- /dev/null
+++ b/client/src/pages/Kitchen.jsx
@@ -0,0 +1,179 @@
+import Ingredient from '../components/Ingredient.jsx'
+import Search from '../components/SearchBar.jsx'
+import api from '../services/api.jsx'
+import { useNavigate } from 'react-router-dom'
+import { useState, useEffect } from 'react'
+
+const Kitchen = () => {
+ const [selectedItem, setSelectedItem] = useState(null);
+
+ const [ingredients, setIngredients] = useState([
+ // {
+ // name: "Eggs",
+ // zone: "Refrigerator",
+ // quantity: 12,
+ // },
+ // {
+ // name: "Rice",
+ // zone: "Pantry",
+ // quantity: "5 lbs",
+ // },
+ ]);
+
+ const navigate = useNavigate()
+
+
+ const handleAddItem = () => {
+ navigate('/addIngredient')
+ }
+
+ const handleEdit = (id) => {
+ navigate(`/editIngredient/${id}`)
+ }
+
+ useEffect(() => {
+ const load = async () => {
+ const data = await api.getKitchen();
+ setIngredients(data)
+ }
+ load()
+ }, [])
+
+ const handleRemove = async (ingredientId) => {
+ await api.removeFromKitchen(ingredientId)
+ setIngredients((prev) => prev.filter((i) => i.ingredient_id !== ingredientId))
+ }
+
+
+
+ return (
+
+ {/*
*/}
+ {/*
My Kitchen */}
+ {/* +Add Ingredient */}
+ {/* */}
+ {/*
*/}
+ {/* */}
+ {/* */}
+ {/**/}
+ {/*
*/}
+
+
My Kitchen
+
+
+
Inventory
+
+ {/* */}
+ +Add Ingredient
+
+
+
+
+
+
+ Ingredient Name
+ Category
+ Calories
+ Dietary facts
+ Quantity
+ Edit
+ Remove
+
+
+
+
+ {Array.isArray(ingredients) && ingredients.map((ingredient) => (
+
+ {ingredient.name}
+ {ingredient.category}
+ {ingredient.calories}
+ {ingredient.dietary_tags}
+ {ingredient.quantity ? `${ingredient.quantity} ${ingredient.unit || ''}` : ''}
+
+ handleEdit(ingredient.ingredient_id)}
+ >Edit
+
+
+ handleRemove(ingredient.ingredient_id)}
+ >Remove
+
+
+ ))}
+
+
+
+
+ )
+}
+
+export default Kitchen
+
+const styles = {
+ pageTitle: {
+ marginLeft: '10px',
+ },
+ topContainer: {
+ display: 'flex',
+ justifyContent: 'space-between',
+ padding: '10px',
+ },
+ innerTopCont: {
+ // padding: '10px',
+ },
+ addbtn: {
+ cursor: 'pointer',
+ marginLeft: '5px',
+ fontSize: '15px',
+ width: '250px',
+ backgroundColor: '#333333',
+ color: 'white',
+ border: 'solid black',
+ padding: '10px',
+ borderRadius: '5px',
+ borderWidth: '1px',
+ textDecoration: 'none',
+ },
+ addButton: {
+ // backgroundColor: "#27F561",
+ fontSize: '15px',
+ color: 'black',
+ border: 'solid black',
+ padding: '20px',
+ borderRadius: '5px',
+ borderWidth: '1px',
+ textDecoration: 'none',
+ margin: '10px',
+ },
+ ingredContainer: {
+ display: 'flex',
+ // padding: '15px',
+ gap: '15px',
+ },
+ table: {
+ width: "100%",
+ borderCollapse: "collapse",
+ },
+ header: {
+ backgroundColor: "#333",
+ color: "white",
+ padding: "12px",
+ textAlign: "left",
+ },
+ cell: {
+ border: "1px solid #ddd",
+ padding: "12px",
+ },
+ tableTitle: {
+ fontSize: '30px',
+ },
+ btn: {
+ cursor: 'pointer',
+ }
+}
diff --git a/client/src/pages/Login.jsx b/client/src/pages/Login.jsx
new file mode 100644
index 000000000..afffa5f5a
--- /dev/null
+++ b/client/src/pages/Login.jsx
@@ -0,0 +1,166 @@
+import { useState, useEffect } from 'react'
+import { useNavigate, useParams, Link } from 'react-router-dom'
+import api from '../services/auth.js'
+import githubimg from '../assets/githubimage.png'
+
+const Login = () => {
+
+ // const navigate = useNavigate()
+ //
+ // const signup = () => {
+ // navigate('/signup')
+ // }
+
+ return (
+
+
EatRite
+
+
Login/Sign up
+
+
+
+ {/* */}
+ {/* */}
+ {/* Email */}
+ {/* */}
+ {/* */}
+ {/*
*/}
+
+ {/* */}
+ {/* */}
+ {/* Password */}
+ {/* */}
+ {/* */}
+ {/*
*/}
+
+
+
+
+ Continue with Github
+
+
+
+ {/* { */}
+ {/* console.log("BUTTON CLICKED") */}
+ {/* api.login() */}
+ {/* }} */}
+ {/* style={styles.btn2} */}
+ {/* > */}
+ {/* Login2 */}
+ {/* */}
+
+ {/* */}
+ {/* */}
+ {/*
Don't have an account?
*/}
+ {/*
signup */}
+ {/*
*/}
+
+
+
+
+
+ )
+}
+
+export default Login
+
+const styles = {
+ gitimg:{
+ height: '20px',
+ },
+ appTitle: {
+ textAlign: 'center',
+ fontSize: '50px',
+ marginTop: '30px',
+ },
+ inForm: {
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '10px',
+ },
+ hr: {
+ marginTop: '20px',
+ marginBottom: '1px',
+ width: '80%',
+ },
+ signupBox: {
+ marginTop: '0px',
+ display: 'block',
+ textAlign: 'center'
+ },
+ title: {
+ display: 'block',
+ fontSize: '30px',
+ margin: '0 auto',
+ },
+ formContainer: {
+ width: '100%',
+ border: 'solid black',
+ borderWidth: '1px',
+ backgroundColor: '#fafafa',
+ borderRadius: '15px',
+ display: 'flex',
+ flexDirection: 'column',
+ justifyContent: 'center',
+ gap: '20px',
+ padding: '20px',
+ fontWeight: 'bold',
+ width: '40%',
+ margin: '50px auto',
+ },
+ inputBox: {
+ display: 'flex',
+ flexDirection: 'column',
+ // border: 'solid black',
+ width:'300px',
+ margin: '0 auto',
+ },
+ input: {
+ border: 'solid black',
+ borderWidth: '1px',
+ width: '290px',
+ height: '30px',
+ borderRadius: '5px',
+ },
+ btn2: {
+ cursor: 'pointer',
+ display: 'flex',
+ gap: '10px',
+ justifyContent:'center',
+ // justifyContent:'space-between',
+ padding: '0 30px',
+ alignItems:'center',
+ width: '300px',
+ height: '50px',
+ fontSize: '15px',
+ fontWeight: 'bold',
+ color: 'white',
+ backgroundColor: '#333333',
+ // padding: '20px',
+ borderRadius: '5px',
+ borderWidth: '1px',
+ textDecoration: 'none',
+ marginTop: '10px',
+ },
+}
diff --git a/client/src/pages/Profile.jsx b/client/src/pages/Profile.jsx
new file mode 100644
index 000000000..1a4c533d6
--- /dev/null
+++ b/client/src/pages/Profile.jsx
@@ -0,0 +1,258 @@
+import { useState, useEffect } from 'react'
+import { Link } from 'react-router-dom'
+import Card from '../components/Card.jsx'
+import EditPreferences from './EditPreferences.jsx'
+import preferenceApi from '../services/preferenceApi.js'
+import favoritesApi from '../services/favoritesApi.js'
+import api from "../services/api.jsx"
+import ProfileIngrCard from '../components/ProfileIngrCard.jsx'
+
+const Profile = ({ user }) => {
+ const [editBox, setEditBox] = useState(false)
+ const [preferences, setPreferences] = useState([])
+ const [favorites, setFavorites] = useState([])
+ const [recipes, setRecipes] = useState([])
+
+ const toggle = () => {
+ if (editBox === false) {
+ setEditBox(true)
+ } else if (editBox === true) {
+ setEditBox(false)
+ }
+ }
+
+ const handleInsertRefresh = (data) => {
+ setPreferences(prev => [
+ ...prev,
+ data
+ ])
+
+ console.log('insert refreshing')
+ }
+
+ const handleDeleteRefresh = (id, type) => {
+ if (type == 'fav') {
+ setFavorites(prev =>
+ prev.filter(favorite => favorite.recipe_id !== id)
+ )
+ setRecipes(prev =>
+ prev.filter(recipe => recipe.id !== id)
+ )
+ console.log('delete fav refreshing')
+ }
+ if (type == 'pref') {
+ setPreferences(prev =>
+ prev.filter(preference => preference.id !== id)
+ )
+ }
+ console.log('delete refreshing')
+ }
+
+ useEffect(() => {
+ const fetchData = async () => {
+ const dataP = await preferenceApi.getPreferences()
+ setPreferences(dataP)
+
+ const dataF = await favoritesApi.getFavorites()
+ setFavorites(dataF)
+
+ const recipePromises = dataF.map((favorite) =>
+ api.getRecipe(favorite.recipe_id))
+
+ const recipeData = await Promise.all(recipePromises)
+
+ setRecipes(recipeData)
+ // console.log(recipeData)
+ }
+
+ fetchData()
+ }, [])
+
+ return (
+
+
+ {editBox && (
)}
+
+
+
+
+
{user.username}
+ {/*
Edit Profile */}
+ {/*
Edit Profile */}
+
+
+
+
+
+
+
Dietary Preferences
+ {/*
Edit Preferences */}
+
Edit Preferences
+
+
+
+ {
+ preferences.map((p) => (
+
{p.preference}
+ ))
+ }
+
+
+
+
+
+
My Kitchen
+ View ➟
+
+
+
Favorite Recipes
+
+ {recipes.map((r) => (
+
+ ))}
+
+ {/*
*/}
+ {/*
*/}
+ {/*
*/}
+ {/*
*/}
+ {/*
*/}
+ {/*
*/}
+ {/*
*/}
+ {/*
*/}
+
+
+
+ )
+}
+
+export default Profile
+
+const styles = {
+ profileBox: {
+ position: 'relative',
+ },
+ info: {
+ display: 'flex',
+ gap: 20,
+ // border: 'solid black',
+ padding: '10px',
+ marginLeft: '10px',
+ marginTop: '20px',
+
+ },
+ favBox: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ // margin: '10px',
+ gap: 10,
+
+ },
+ prefBox: {
+ display: 'flex',
+ flexDirection: 'column',
+ // justifyContent: 'space-between',
+ border: 'solid black',
+ borderRadius: '10px',
+ padding: '10px',
+ // height: '150px',
+ marginTop: '20px',
+ marginLeft: '10px',
+ },
+ kitchenBox: {
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ paddingLeft: '40px',
+ paddingRight: '40px',
+ border: 'solid black',
+ borderRadius: '10px',
+ height: '100px',
+ marginTop: '20px',
+ marginLeft: '10px',
+ },
+ prefs: {
+ display: 'flex',
+ flexWrap: 'wrap',
+ // alignItems: 'center',
+ gap: 10,
+ marginLeft: '30px',
+ marginTop: '10px',
+ },
+ itemPref: {
+ border: 'solid black',
+ borderRadius: '15px',
+ textAlign: 'center',
+ fontSize: '20px',
+ width: '125px',
+ height: '30px',
+ backgroundColor: '#dcdcdc',
+ },
+ innerPref: {
+ display: 'flex',
+ width: '100%',
+ justifyContent: 'space-between',
+ // padding: '15px',
+ },
+ infoImg: {
+ display:'block',
+ width: '100px',
+ borderRadius: '50%',
+ },
+ username: {
+ fontSize: '30px',
+ marginBottom: '15px',
+ },
+ dietTitle: {
+ display: 'block',
+ fontSize: '25px',
+ marginLeft: '20px',
+ },
+ favTitle: {
+ fontSize: '25px',
+ marginLeft: '20px',
+ },
+ editBtn: {
+ textDecoration: 'none',
+ color: 'black',
+ cursor: 'pointer',
+ height: '40px',
+ width: '100px',
+ border: 'solid black',
+ borderRadius: '5px',
+ borderWidth:'1px',
+ padding: '10px',
+ // marginRight: '25px',
+ // marginTop: '10px',
+ },
+ editPrefBtn: {
+ fontSize:'20px',
+ marginRight: '25px',
+ marginTop: '25px',
+ textDecoration: 'none',
+ color: 'black',
+ cursor: 'pointer',
+ height: '45px',
+ width: '180px',
+ border: 'solid black',
+ borderRadius: '5px',
+ borderWidth:'1px',
+ padding: '10px',
+ },
+ kitchenLink: {
+ fontSize: '25px',
+ textDecoration: 'none',
+ color: 'black',
+ }
+
+}
diff --git a/client/src/pages/Recipe.jsx b/client/src/pages/Recipe.jsx
new file mode 100644
index 000000000..28464e011
--- /dev/null
+++ b/client/src/pages/Recipe.jsx
@@ -0,0 +1,122 @@
+import react from 'react'
+import api from "../services/api.jsx"
+import { useState, useEffect } from 'react'
+import { useParams } from 'react-router'
+
+const Recipe = () => {
+ const [recipe, setRecipe] = useState([])
+ const params = useParams()
+
+ useEffect(() => {
+ const loadRecipe = async () => {
+ const data = await api.getRecipe(params.id)
+ setRecipe(data)
+ }
+
+ loadRecipe()
+ }, [])
+
+ return (
+
+
{recipe?.title}
+
+ {/*
Description */}
+
+
+
+
+
+
{recipe?.description}
+
+
+
+
+
+
+
Ingredience
+
+ {/* {recipe.} */}
+
+
+
+
+
Directions
+
+ {recipe.instructions}
+
+
+
+
+
+ )
+}
+
+export default Recipe
+
+const styles = {
+ recipeBox: {
+ display: 'flex',
+ gap: 10,
+ width: '90%',
+ // border: 'solid black',
+ // padding: '20px',
+ boxSizing: 'border-box',
+ marginLeft: '10px',
+ },
+ descrBox: {
+ display: 'flex',
+ alignItems: 'center',
+ flexWrap: 'wrap',
+ gap: 60,
+ marginLeft: '2px',
+ fontSize: '20px',
+ // border: 'solid black',
+ // borderRadius: '10px',
+ // borderWidth: '1px',
+ // paddingLeft: '5px',
+ padding: '10px',
+ },
+ ingrBox: {
+ width: '45%',
+ marginRight: '10px',
+ },
+ dirBox: {
+ width: '45%',
+ },
+ innerIngr: {
+ display: 'flex',
+ flexDirection: 'column',
+ border: 'solid black',
+ borderWidth: '1px',
+ borderRadius: '10px',
+ width: '100%',
+ height: '500px',
+ },
+ innerDir: {
+ display: 'flex',
+ flexDirection: 'column',
+ border: 'solid black',
+ borderWidth: '1px',
+ borderRadius: '10px',
+ width: '100%',
+ height: '500px',
+ padding: '10px',
+ },
+ img: {
+ display: 'block',
+ width: '200px',
+ },
+ imgBox: {
+ border: 'solid black',
+ borderWidth: '1px',
+ borderRadius: '10px',
+ padding: '10px',
+ },
+ infoBox: {
+ // border: 'solid black',
+ // borderWidth: '1px',
+ // borderRadius: '10px',
+ // padding: '10px',
+ },
+
+}
diff --git a/client/src/services/api.jsx b/client/src/services/api.jsx
new file mode 100644
index 000000000..c4735deca
--- /dev/null
+++ b/client/src/services/api.jsx
@@ -0,0 +1,151 @@
+const API_URL = "http://localhost:3000/api"
+
+const addIngredient = async (options) => {
+ try {
+ const response = await fetch(`${API_URL}/create/ingredient`,options)
+ window.location = '/kitchen'
+ } catch (err) {
+ console.error(err)
+ }
+}
+
+const editIngredient = async (id, options) => {
+ const response = await fetch(`${API_URL}/edit/ingredient`, options)
+ const data = await response.json()
+ window.location = '/kitchen'
+ return data
+}
+
+const getIngredients = async () => {
+ try {
+ const response = await fetch(`${API_URL}/ingredients`)
+ const data = await response.json()
+ return data
+ } catch(err) {
+ console.error(err)
+ }
+}
+
+const getIngredient = async (id) => {
+ try {
+ const response = await fetch(`${API_URL}/ingredient/${id}`)
+ const data = await response.json()
+ return data
+ } catch(err) {
+ console.error(err)
+ }
+}
+
+const updateIngredient = async (id, options) => {
+ try {
+ const response = await fetch(`${API_URL}/ingredient/${id}`, options)
+ const data = await response.json()
+ window.location = '/kitchen'
+ return data
+ } catch (err) {
+ console.error(err)
+ }
+}
+
+const getKitchen = async () => {
+ try {
+ const response = await fetch(`${API_URL}/kitchen`, { credentials: 'include' })
+ const data = await response.json()
+ return data
+ } catch (err) {
+ console.error(err)
+ }
+}
+
+const addToKitchen = async (ingredient) => {
+ try {
+ const response = await fetch(`${API_URL}/create/kitchen-item`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ credentials: 'include',
+ body: JSON.stringify(ingredient)
+ })
+ const data = await response.json()
+ window.location = '/kitchen'
+ return data
+ } catch (err) {
+ console.error(err)
+ }
+}
+
+const removeFromKitchen = async (ingredientId) => {
+ try {
+ const response = await fetch(`${API_URL}/delete/kitchen-item/${ingredientId}`, {
+ method: 'DELETE',
+ credentials: 'include'
+ })
+ return await response.json()
+ } catch (err) {
+ console.error(err)
+ }
+}
+
+const getRecipes = async () => {
+ try {
+ const response = await fetch(`${API_URL}/recipes`)
+ const data = await response.json()
+ return data
+ } catch (err) {
+ console.log(err)
+ }
+}
+
+const getRecipe = async (id) => {
+ try {
+ const response = await fetch(`${API_URL}/recipe/${id}`)
+ const data = await response.json()
+ return data
+ } catch (err) {
+ console.log(err)
+ }
+}
+
+const createRecipe = async (recipe) => {
+ try {
+ const response = await fetch(`${API_URL}/create/recipe`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ credentials: 'include',
+ body: JSON.stringify(recipe)
+ })
+ const data = await response.json()
+ return data
+ } catch (err) {
+ console.log(err)
+ }
+}
+
+const deleteRecipe = async (id) => {
+ try {
+ const response = await fetch(`${API_URL}/delete/recipe/${id}`, {
+ method: 'DELETE',
+ credentials: 'include'
+ })
+ const data = await response.json()
+ return data
+ } catch (err) {
+ console.log(err)
+ }
+}
+
+export default {
+ addIngredient,
+ getIngredients,
+ getIngredient,
+ editIngredient,
+ updateIngredient,
+
+ getKitchen,
+ addToKitchen,
+ removeFromKitchen,
+
+ getRecipes,
+ getRecipe,
+ createRecipe,
+ deleteRecipe
+}
diff --git a/client/src/services/auth.js b/client/src/services/auth.js
new file mode 100644
index 000000000..12455bfaf
--- /dev/null
+++ b/client/src/services/auth.js
@@ -0,0 +1,18 @@
+const API_URL = "http://localhost:3000"
+
+const login = () => {
+ console.log('login')
+ window.location.href = `${API_URL}/auth/github`
+}
+
+const logout = async () => {
+ const url = `${API_URL}/auth/logout`
+ const response = await fetch(url, { credentials: 'include' })
+ const json = await response.json()
+ window.location.href='/login'
+}
+
+export default {
+ login,
+ logout
+}
diff --git a/client/src/services/favoritesApi.js b/client/src/services/favoritesApi.js
new file mode 100644
index 000000000..28b4cf350
--- /dev/null
+++ b/client/src/services/favoritesApi.js
@@ -0,0 +1,51 @@
+const API_URL = "http://localhost:3000/api"
+
+const createFavorite = async (recipe_id) => {
+ try {
+ const response = await fetch(`${API_URL}/create/favorite`, {
+ method: "POST",
+ credentials: 'include',
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ recipe_id: recipe_id
+ }),
+ })
+ console.log('creating favorite ...')
+ const data = await response.json()
+ return data
+ } catch (err) {
+ console.log(err)
+ }
+}
+
+const getFavorites = async () => {
+ try {
+ const result = await fetch(`${API_URL}/favorites`, { credentials: 'include' })
+ const data = await result.json()
+ return data
+ } catch (err) {
+ console.log(err)
+ }
+}
+
+const deleteFavorite = async (id) => {
+ try {
+ console.log(id)
+ const response = await fetch(`${API_URL}/delete/favorite/${id}`, {
+ method: "DELETE",
+ credentials: 'include',
+ })
+ const data = await response.json()
+ return data
+ } catch (err) {
+ console.log(err)
+ }
+}
+
+export default {
+ createFavorite,
+ getFavorites,
+ deleteFavorite,
+}
diff --git a/client/src/services/preferenceApi.js b/client/src/services/preferenceApi.js
new file mode 100644
index 000000000..33d8e637f
--- /dev/null
+++ b/client/src/services/preferenceApi.js
@@ -0,0 +1,48 @@
+const API_URL = "http://localhost:3000/api"
+
+const getPreferences = async () => {
+ try {
+ const result = await fetch(`${API_URL}/preferences`, { credentials: 'include' })
+ const data = result.json()
+ return data
+ } catch (err) {
+ console.log(err)
+ }
+}
+
+const createPreference = async (preference) => {
+ try {
+ const result = await fetch(`${API_URL}/create/preference`, {
+ method: 'POST',
+ credentials: 'include' ,
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(preference)
+ }
+ )
+ const data = await result.json()
+ return data
+ } catch (err) {
+ console.log(err)
+ }
+}
+
+const deletePreference = async (id) => {
+ try {
+ const response = await fetch(`${API_URL}/delete/preference/${id}`, {
+ method: "DELETE",
+ credentials: 'include',
+ })
+ const data = await response.json()
+ return data
+ } catch (err) {
+ console.log(err)
+ }
+}
+
+export default {
+ getPreferences,
+ createPreference,
+ deletePreference
+}
diff --git a/client/src/services/scheduledMealsApi.js b/client/src/services/scheduledMealsApi.js
new file mode 100644
index 000000000..ddc6fcd06
--- /dev/null
+++ b/client/src/services/scheduledMealsApi.js
@@ -0,0 +1,45 @@
+const API_URL = "http://localhost:3000/api"
+
+const getScheduledMeals = async () => {
+ try {
+ const response = await fetch(`${API_URL}/scheduled-meals`, { credentials: 'include' })
+ const data = await response.json()
+ return data
+ } catch (err) {
+ console.error(err)
+ }
+}
+
+const createScheduledMeal = async (meal) => {
+ try {
+ const response = await fetch(`${API_URL}/create/scheduled-meal`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ credentials: 'include',
+ body: JSON.stringify(meal)
+ })
+ const data = await response.json()
+ return data
+ } catch (err) {
+ console.error(err)
+ }
+}
+
+const deleteScheduledMeal = async (id) => {
+ try {
+ const response = await fetch(`${API_URL}/delete/scheduled-meal/${id}`, {
+ method: 'DELETE',
+ credentials: 'include'
+ })
+ const data = await response.json()
+ return data
+ } catch (err) {
+ console.error(err)
+ }
+}
+
+export default {
+ getScheduledMeals,
+ createScheduledMeal,
+ deleteScheduledMeal,
+}
diff --git a/client/src/services/userApi.jsx b/client/src/services/userApi.jsx
new file mode 100644
index 000000000..4b1c5af29
--- /dev/null
+++ b/client/src/services/userApi.jsx
@@ -0,0 +1,15 @@
+const API_URL = "http://localhost:3000/api"
+
+const getUser = async (id) => {
+ try {
+ const response = await fetch(`${API_URL}/users/${id}`)
+ const data = response.json()
+ return data
+ } catch (err) {
+ console.log(err)
+ }
+}
+
+export default {
+ getUser
+}
diff --git a/client/vite.config.js b/client/vite.config.js
new file mode 100644
index 000000000..139c559ff
--- /dev/null
+++ b/client/vite.config.js
@@ -0,0 +1,13 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+
+// https://vite.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ watch: {
+ usePolling: true,
+ interval: 100,
+ }
+ }
+})
diff --git a/milestones/milestone1.md b/milestones/milestone1.md
index 52b9b0038..eb78f6a09 100644
--- a/milestones/milestone1.md
+++ b/milestones/milestone1.md
@@ -6,27 +6,28 @@ This document should be completed and submitted during **Unit 5** of this course
This unit, be sure to complete all tasks listed below. To complete a task, place an `x` between the brackets.
-- [ ] Read and understand all required features
- - [ ] Understand you **must** implement **all** baseline features and **two** custom features
-- [ ] In `readme.md`: update app name to your app's name
-- [ ] In `readme.md`: add all group members' names
-- [ ] In `readme.md`: complete the **Description and Purpose** section
-- [ ] In `readme.md`: complete the **Inspiration** section
-- [ ] In `readme.md`: list a name and description for all features (minimum 6 for full points) you intend to include in your app (in future units, you will check off features as you complete them and add GIFs demonstrating the features)
-- [ ] In `planning/user_stories.md`: add all user stories (minimum 10 for full points)
-- [ ] In `planning/user_stories.md`: use 1-3 unique user roles in your user stories
-- [ ] In this document, complete all thre questions in the **Reflection** section below
+- [x] Read and understand all required features
+ - [x] Understand you **must** implement **all** baseline features and **two** custom features
+- [x] In `readme.md`: update app name to your app's name
+- [x] In `readme.md`: add all group members' names
+- [x] In `readme.md`: complete the **Description and Purpose** section
+- [x] In `readme.md`: complete the **Inspiration** section
+- [x] In `readme.md`: list a name and description for all features (minimum 6 for full points) you intend to include in your app (in future units, you will check off features as you complete them and add GIFs demonstrating the features)
+- [x] In `planning/user_stories.md`: add all user stories (minimum 10 for full points)
+- [x] In `planning/user_stories.md`: use 1-3 unique user roles in your user stories
+- [x] In this document, complete all thre questions in the **Reflection** section below
## Reflection
### 1. What went well during this unit?
-[👉🏾👉🏾👉🏾 your answer here]
+Recapping everything we learned through the prior units.
### 2. What were some challenges your group faced in this unit?
-[👉🏾👉🏾👉🏾 your answer here]
+We struggled a bit with generating ideas and collaborating, but were able to manage a rough draft for our project.
### 3. What additional support will you need in upcoming units as you continue to work on your final project?
-[👉🏾👉🏾👉🏾 your answer here]
+I think we need to communicate more amongst our team. It would be nice if we could spend a portion of our lab time on project collaboration.
+
diff --git a/milestones/milestone2.md b/milestones/milestone2.md
index e3178cd81..122bf0e12 100644
--- a/milestones/milestone2.md
+++ b/milestones/milestone2.md
@@ -6,24 +6,24 @@ This document should be completed and submitted during **Unit 6** of this course
This unit, be sure to complete all tasks listed below. To complete a task, place an `x` between the brackets.
-- [ ] In `planning/wireframes.md`: add wireframes for at least three pages in your web app.
- - [ ] Include a list of pages in your app
-- [ ] In `planning/entity_relationship_diagram.md`: add the entity relationship diagram you developed for your database.
- - [ ] Your entity relationship diagram should include the tables in your database.
-- [ ] Prepare your three-minute pitch presentation, to be presented during Unit 7 (the next unit).
- - [ ] You do **not** need to submit any materials in advance of your pitch.
-- [ ] In this document, complete all three questions in the **Reflection** section below
+- [x] In `planning/wireframes.md`: add wireframes for at least three pages in your web app.
+ - [x] Include a list of pages in your app
+- [x] In `planning/entity_relationship_diagram.md`: add the entity relationship diagram you developed for your database.
+ - [x] Your entity relationship diagram should include the tables in your database.
+- [x] Prepare your three-minute pitch presentation, to be presented during Unit 7 (the next unit).
+ - [x] You do **not** need to submit any materials in advance of your pitch.
+- [x] In this document, complete all three questions in the **Reflection** section below
## Reflection
### 1. What went well during this unit?
-[👉🏾👉🏾👉🏾 your answer here]
+Wireframing our key pages first gave the whole team a shared picture of what we're building, which made designing the database much easier. Walking through our features one by one, we were able to identify the core entities (users, ingredients, recipes) and reason out where each many-to-many relationship needed a join table.
### 2. What were some challenges your group faced in this unit?
-[👉🏾👉🏾👉🏾 your answer here]
+The trickiest part was the entity relationship diagram. We initially wanted to store things like calories and star ratings as single columns, but realized that data like a user's rating or the quantity of an ingredient in a recipe depends on *two* tables at once, so it belongs on a join table instead. Deciding what deserved its own table versus a column took a few iterations.
### 3. What additional support will you need in upcoming units as you continue to work on your final project?
-[👉🏾👉🏾👉🏾 your answer here]
+As we move into development, we'd appreciate guidance on translating this ERD into actual PostgreSQL schema/migration files and wiring the Express API routes to it. Some dedicated lab time for team collaboration would also help us divide the feature work and stay in sync.
diff --git a/milestones/milestone3.md b/milestones/milestone3.md
index b45ad8561..17693fe40 100644
--- a/milestones/milestone3.md
+++ b/milestones/milestone3.md
@@ -8,44 +8,53 @@ This unit, be sure to complete all tasks listed below. To complete a task, place
You will need to reference the GitHub Project Management guide in the course portal for more information about how to complete each of these steps.
-- [ ] In your repo, create a project board.
+- [x] In your repo, create a project board.
- *Please be sure to share your project board with the grading team's GitHub **codepathreview**. This is separate from your repository's sharing settings.*
-- [ ] In your repo, create at least 5 issues from the features on your feature list.
+ - Project Board: https://github.com/orgs/codepath-web103-group23/projects/3/views/2
+- [x] In your repo, create at least 5 issues from the features on your feature list.
- List the title of each issue you created:
- 1.
- 2.
- 3.
- 4.
- 5.
-- [ ] In your repo, update the status of issues in your project board.
-- [ ] In your repo, create a GitHub Milestone for each final project unit, corresponding to each of the 5 milestones in your `milestones/` directory.
+
+ 1. [ Create ] Navbar (uniform amongst all pages) (https://github.com/codepath-web103-group23/web103_finalproject/issues/24)
+ 2. [ Create ] Cards for recipe suggestions (https://github.com/codepath-web103-group23/web103_finalproject/issues/23)
+ 3. [ Create ] Home page (first feature) (https://github.com/codepath-web103-group23/web103_finalproject/issues/14)
+ 4. Start project /client (react frontend) /server (express backend) skeleton (https://github.com/codepath-web103-group23/web103_finalproject/issues/13)
+ 5. [ Create ] User kitchen Page (https://github.com/codepath-web103-group23/web103_finalproject/issues/18)
+
+- [x] In your repo, update the status of issues in your project board.
+- [x] In your repo, create a GitHub Milestone for each final project unit, corresponding to each of the 5 milestones in your `milestones/` directory.
- List the name of each milestone you created:
- 1.
- 2.
- 3.
+
+ 1. Milestone 1 - Unit 5 (https://github.com/codepath-web103-group23/web103_finalproject/milestone/1)
+ 2. Milestone 2 - Unit 6 (https://github.com/codepath-web103-group23/web103_finalproject/milestone/2)
+ 3. Milestone 3 - Unit 7 (https://github.com/codepath-web103-group23/web103_finalproject/milestone/3)
+ 4. Milestone 4 - Unit 8 (https://github.com/codepath-web103-group23/web103_finalproject/milestone/4)
+ 5. Milestone 5 - Unit 9 (https://github.com/codepath-web103-group23/web103_finalproject/milestone/5)
+
- [ ] Set the completion percentage of each milestone. The GitHub Milestone for this unit (Milestone 3 - Unit 7) should be 100% completed when you submit for full points.
-- [ ] In `readme.md`, check off the features you have completed in this unit by adding a ✅ emoji in front of the feature's name.
- - [ ] Under each feature you have completed, include a GIF showing feature functionality.
-- [ ] In this documents, complete all five questions in the **Reflection** section below.
+ - https://github.com/codepath-web103-group23/web103_finalproject/milestone/3
+
+- [x] In `readme.md`, check off the features you have completed in this unit by adding a ✅ emoji in front of the feature's name.
+ - [x] Under each feature you have completed, include a GIF showing feature functionality.
+- [x] In this documents, complete all five questions in the **Reflection** section below.
## Reflection
### 1. What went well during this unit?
-[👉🏾👉🏾👉🏾 your answer here]
+Learning github project management with organizations, projects, project boards, workflows, etc. was a great learning experience.
### 2. What were some challenges your group faced in this unit?
-[👉🏾👉🏾👉🏾 your answer here]
+I needed to add my existing repo to an organization and it took a while to understand how to setup all the permission stuff.
### Did you finish all of your tasks in your sprint plan for this week? If you did not finish all of the planned tasks, how would you prioritize the remaining tasks on your list?
-[👉🏾👉🏾👉🏾 your answer here]
+Yes we did finish all our tasks assigned for our milestone this week. We started the base of the project and developed the home page and navigation.
### Which features and user stories would you consider “at risk”? How will you change your plan if those items remain “at risk”?
-[👉🏾👉🏾👉🏾 your answer here]
+I think the calendar, and login may be too much to manage, but as we build out our features there may be time.
### 5. What additional support will you need in upcoming units as you continue to work on your final project?
-[👉🏾👉🏾👉🏾 your answer here]
+I think the key to our success is continious communication amongst team members. As long as we are focused and organized we should make good progress.
diff --git a/milestones/milestone4.md b/milestones/milestone4.md
index 61dc73bb9..94c516019 100644
--- a/milestones/milestone4.md
+++ b/milestones/milestone4.md
@@ -6,29 +6,29 @@ This document should be completed and submitted during **Unit 8** of this course
This unit, be sure to complete all tasks listed below. To complete a task, place an `x` between the brackets.
-- [ ] Update the completion percentage of each GitHub Milestone. The milestone for this unit (Milestone 4 - Unit 8) should be 100% completed when you submit for full points.
-- [ ] In `readme.md`, check off the features you have completed in this unit by adding a ✅ emoji in front of the feature's name.
- - [ ] Under each feature you have completed, include a GIF showing feature functionality.
-- [ ] In this document, complete all five questions in the **Reflection** section below.
+- [x] Update the completion percentage of each GitHub Milestone. The milestone for this unit (Milestone 4 - Unit 8) should be 100% completed when you submit for full points.
+- [x] In `readme.md`, check off the features you have completed in this unit by adding a ✅ emoji in front of the feature's name.
+ - [x] Under each feature you have completed, include a GIF showing feature functionality.
+- [x] In this document, complete all five questions in the **Reflection** section below.
## Reflection
### 1. What went well during this unit?
-[👉🏾👉🏾👉🏾 your answer here]
+The wireframes helped a lot with creating the pages layout.
### 2. What were some challenges your group faced in this unit?
-[👉🏾👉🏾👉🏾 your answer here]
+I think we are a bit behind and realizing every feature requires a lot of attention.
### Did you finish all of your tasks in your sprint plan for this week? If you did not finish all of the planned tasks, how would you prioritize the remaining tasks on your list?
-[👉🏾👉🏾👉🏾 your answer here]
+Yes we did complete all the tasks for this week, however we had to push the login and profile features to the next milestone.
### Which features and user stories would you consider “at risk”? How will you change your plan if those items remain “at risk”?
-[👉🏾👉🏾👉🏾 your answer here]
+I think the calendar and login may be difficult to accomplish since they require a lot of attetion to detail and setup.
### 5. What additional support will you need in upcoming units as you continue to work on your final project?
-[👉🏾👉🏾👉🏾 your answer here]
+As I said before, our team would benefit from stronger communication and spreading out the workload.
diff --git a/milestones/progress_report_3.md b/milestones/progress_report_3.md
new file mode 100644
index 000000000..3f26459db
--- /dev/null
+++ b/milestones/progress_report_3.md
@@ -0,0 +1,55 @@
+# Milestone 3 Progress Report
+
+## GitHub Project Setup
+Project board created: yes (https://github.com/orgs/codepath-web103-group23/projects/3)
+
+Project board shared with codepathreview:
+- Project board is public: https://github.com/orgs/codepath-web103-group23/projects/3/views/2
+- codepath review needs to accept the invite to my organization **before I can inviet them to the project board**
+
+
+
+
+
+## Issues Created
+List the title of each issue you created, one per line:
+- Start project /client (react frontend) /server (express backend) skeleton (https://github.com/codepath-web103-group23/web103_finalproject/issues/13)
+- [ Create ] Home page (first feature) (https://github.com/codepath-web103-group23/web103_finalproject/issues/14)
+- [ Create ] Cards for recipe suggestions (https://github.com/codepath-web103-group23/web103_finalproject/issues/23)
+- [ Create ] Navbar (uniform amongst all pages) (https://github.com/codepath-web103-group23/web103_finalproject/issues/24)
+- [ Create ] database on render based on ERD (https://github.com/codepath-web103-group23/web103_finalproject/issues/15)
+- [ Server ] setup .env(w/ render credentials), and database pool (https://github.com/codepath-web103-group23/web103_finalproject/issues/16)
+- [ Finish ] - Check website completes all user stories (https://github.com/codepath-web103-group23/web103_finalproject/issues/17)
+- [ Create ] User kitchen Page (https://github.com/codepath-web103-group23/web103_finalproject/issues/18)
+- [ Create ] Ingredient input page (https://github.com/codepath-web103-group23/web103_finalproject/issues/19)
+- [ Create ] User Profile page (https://github.com/codepath-web103-group23/web103_finalproject/issues/20)
+- [ Create ] Login Page (https://github.com/codepath-web103-group23/web103_finalproject/issues/21)
+- [ Create ] Meal Calendar page (https://github.com/codepath-web103-group23/web103_finalproject/issues/22)
+- [ Deploy ] App on Render (https://github.com/codepath-web103-group23/web103_finalproject/issues/25)
+
+## GitHub Milestones Created
+List the name of each GitHub Milestone you created, one per line:
+- Milestone 1 - Unit 5 (https://github.com/codepath-web103-group23/web103_finalproject/milestone/1)
+- Milestone 2 - Unit 6 (https://github.com/codepath-web103-group23/web103_finalproject/milestone/2)
+- Milestone 3 - Unit 7 (https://github.com/codepath-web103-group23/web103_finalproject/milestone/3)
+- Milestone 4 - Unit 8 (https://github.com/codepath-web103-group23/web103_finalproject/milestone/4)
+- Milestone 5 - Unit 9 (https://github.com/codepath-web103-group23/web103_finalproject/milestone/5)
+
+## Milestone 3 Completion
+Replace the blank before the percent sign with a number from 0 to 100.
+
+- Completion percentage for Milestone 1 - Unit 5: 100%
+- Completion percentage for Milestone 2 - Unit 6: 100%
+- Completion percentage for Milestone 3 - Unit 7: 100%
+- Completion percentage for Milestone 4 - Unit 8: 0%
+- Completion percentage for Milestone 5 - Unit 9: 0%
+
+List each issue you completed this unit and the main file or folder where that work lives, one per line:
+- Home Page - client/src/pages/Home.jsx
+- Nav bar - client/src/components/Nav.jsx
+- Search bar - client/src/components/SearchBar.jsx
+- User Feed cards - client/src/components/Card.jsx
+
+## Features Completed This Unit
+List each feature you completed and checked off in readme.md this unit, one per line:
+- Home page featuring recipe feed
diff --git a/milestones/progress_report_4.md b/milestones/progress_report_4.md
new file mode 100644
index 000000000..7b300c6aa
--- /dev/null
+++ b/milestones/progress_report_4.md
@@ -0,0 +1,18 @@
+# Milestone 4 Progress Report
+
+## Milestone 4 Completion
+Replace the blank before the percent sign with a number from 0 to 100.
+
+Completion percentage for Milestone 4 - Unit 8: %100
+
+List each issue you completed this unit and the main file or folder where that work lives, one per line:
+- [ Create ] Ingredient input page - client/src/pages/AddIngredient.jsx
+- [ Create ] User kitchen Page - client/src/pages/Kitchen.jsx
+- [ Create ] database on render based on ERD - server/db/schema.js
+- [ Server ] setup .env(w/ render credentials), and database pool - server/db/dbpool.js
+- [ routes ] add recipes - server/models/recipeModel.js, server/controllers/recipeController.js, server/routes/recipeRoutes.js
+
+## Features Completed This Unit
+List each feature you completed and checked off in readme.md this unit, one per line:
+- User kitchen Page
+- Ingredients input page
diff --git a/planning/artifact.html b/planning/artifact.html
new file mode 100644
index 000000000..5a5f753c5
--- /dev/null
+++ b/planning/artifact.html
@@ -0,0 +1,321 @@
+EatRite — Pitch Deck
+
+
+❧ EatRite
+1 / 6
+
+
+
+
+
+ 01 The pitch
+ Cook what youalready have.
+ EatRite turns the ingredients in your kitchen into meals you can make right now.
+
+
+
+
+ 02 The problem
+ A full fridge, and still “nothing to eat.”
+
+
· Same five recipes, on repeat
People fall back on what they already know instead of trying something new.
+
· Ideas are scattered across the web
Finding a meal means tab-hopping between a dozen recipe sites.
+
· Ingredients go to waste
Food expires in the back of the fridge because no recipe ever used it.
+
+
+
+
+
+ 03 How it works
+ From your kitchen to your plate in four steps.
+
+
1 Stock your kitchen
Add the ingredients you have on hand and set your dietary preferences.
+
2 Get matched recipes
The home page surfaces recipes you can actually make, sorted by rating.
+
3 Save your favorites
Keep the recipes you love one click away in your profile.
+
4 Plan the week
Drop meals onto the calendar and track what you’ve eaten.
+
+
+
+
+
+ 04 Under the hood
+
+
+
Three entities, tied together.
+
Users, recipes, and ingredients — connected by join tables that power the “cook with what I have” match.
+
+ React
+ Express
+ PostgreSQL
+ Render
+
+
+
+
users entity
+
recipes entity
+
ingredients entity
+
recipe_ingredients join
+
kitchen join
+
favorites · ratings · scheduled_meals join
+
+
+
+
+
+
+ 05 What we’re building
+ Baseline, custom, and one feature that makes us us .
+
+
Baseline Recipe browsing Browse, search, and sort every recipe by rating, newest, or favorites.
+
Custom My Kitchen Track the ingredients you own and manage them from your profile.
+
Stretch Cook with what I have Match your kitchen against recipes to show exactly what you can make tonight.
+
+
+
+
+
+ 06 The team
+ Six of us, one fuller fridge.
+ Built for WEB103 — React front end, Express API, and a Postgres database deployed on Render.
+
+ Ashok Gaire
+ Ousseynou Diop
+ Mohammad Naeem
+ Khadeja Ahmar
+ Oscar Mochizaki
+ Noah Mckegney
+
+ Stop staring into the fridge. Start eating right.
+
+
+
+
+‹
+›
+
+← → or space to navigate
+
+
diff --git a/planning/entity_relationship_diagram.md b/planning/entity_relationship_diagram.md
index 12c25f62c..661f9a58f 100644
--- a/planning/entity_relationship_diagram.md
+++ b/planning/entity_relationship_diagram.md
@@ -4,14 +4,98 @@ Reference the Creating an Entity Relationship Diagram final project guide in the
## Create the List of Tables
-[👉🏾👉🏾👉🏾 List each table in your diagram]
+**Entities (core "things"):**
+- `users`
+- `ingredients`
+- `recipes`
+
+**Join tables (resolve many-to-many relationships):**
+- `recipe_ingredients` — which ingredients (and how much) make up each recipe
+- `kitchen` — which ingredients a user currently has on hand
+- `favorites` — which recipes a user has saved
+- `ratings` — the star rating a user gave a recipe
+- `scheduled_meals` — recipes a user has scheduled on the meal calendar
## Add the Entity Relationship Diagram
-[👉🏾👉🏾👉🏾 Include an image or images of the diagram below. You may also wish to use the following markdown syntax to outline each table, as per your preference.]
+
+
+## Table Details
+
+### users
+| Column Name | Type | Description |
+|-------------|------|-------------|
+| id | integer | primary key |
+| name | text | user's display name |
+| email | text | login email (unique) |
+| password_hash | text | hashed password for authentication |
+| dietary_preferences | text | user's dietary tags used to filter recipes |
+
+### ingredients
+| Column Name | Type | Description |
+|-------------|------|-------------|
+| id | integer | primary key |
+| name | text | ingredient name |
+| category | text | e.g. produce, dairy, protein, grain |
+| calories | integer | calories per unit (used to estimate meal nutrition) |
+| dietary_tags | text | e.g. vegan, gluten-free, contains-nuts |
+
+### recipes
+| Column Name | Type | Description |
+|-------------|------|-------------|
+| id | integer | primary key |
+| title | text | recipe name |
+| description | text | short summary shown on the recipe card |
+| instructions | text | step-by-step preparation steps |
+| image_url | text | photo of the finished dish |
+| avg_rating | numeric | average of all user ratings (computed from `ratings`) |
+
+### recipe_ingredients (join: recipes ↔ ingredients)
+| Column Name | Type | Description |
+|-------------|------|-------------|
+| id | integer | primary key |
+| recipe_id | integer | foreign key → recipes.id |
+| ingredient_id | integer | foreign key → ingredients.id |
+| quantity | numeric | amount of the ingredient in this recipe |
+| unit | text | unit for the quantity (cups, grams, tbsp) |
+
+### kitchen (join: users ↔ ingredients)
+| Column Name | Type | Description |
+|-------------|------|-------------|
+| id | integer | primary key |
+| user_id | integer | foreign key → users.id |
+| ingredient_id | integer | foreign key → ingredients.id |
+| quantity | numeric | amount the user has on hand |
+| unit | text | unit for the quantity |
+
+### favorites (join: users ↔ recipes)
+| Column Name | Type | Description |
+|-------------|------|-------------|
+| id | integer | primary key |
+| user_id | integer | foreign key → users.id |
+| recipe_id | integer | foreign key → recipes.id |
+
+### ratings (join: users ↔ recipes)
+| Column Name | Type | Description |
+|-------------|------|-------------|
+| id | integer | primary key |
+| user_id | integer | foreign key → users.id |
+| recipe_id | integer | foreign key → recipes.id |
+| stars | integer | star rating this user gave this recipe (1–5) |
+### scheduled_meals (join: users ↔ recipes)
| Column Name | Type | Description |
|-------------|------|-------------|
| id | integer | primary key |
-| name | text | name of the shoe model |
-| ... | ... | ... |
+| user_id | integer | foreign key → users.id |
+| recipe_id | integer | foreign key → recipes.id |
+| date | date | day the meal is scheduled / was eaten |
+| meal_type | text | breakfast, lunch, dinner, or snack |
+
+## Relationship Summary
+
+- A **user** has many **kitchen** entries; each **ingredient** can appear in many users' kitchens → many-to-many via `kitchen`.
+- A **recipe** has many **recipe_ingredients**; each **ingredient** can appear in many recipes → many-to-many via `recipe_ingredients` (carries `quantity` + `unit`).
+- A **user** can favorite many **recipes**; a **recipe** can be favorited by many users → many-to-many via `favorites`.
+- A **user** can rate many **recipes**; a **recipe** can be rated by many users → many-to-many via `ratings` (carries `stars`).
+- A **user** can schedule many **recipes** across dates → many-to-many via `scheduled_meals` (carries `date` + `meal_type`).
diff --git a/planning/erd.svg b/planning/erd.svg
new file mode 100644
index 000000000..6208dbd0e
--- /dev/null
+++ b/planning/erd.svg
@@ -0,0 +1,139 @@
+
+
+ EatRite — Entity Relationship Diagram
+
+
+
+ PK = Primary Key • FK = Foreign Key
+ ─── one-to-many (1 ── ∞)
+ Join tables resolve many-to-many links
+
+
+
+
+
+ users
+ id (PK)
+ name
+ email
+ password_hash
+ dietary_preferences
+
+
+
+
+
+
+ ingredients
+ id (PK)
+ name
+ category
+ calories
+ dietary_tags
+
+
+
+
+
+
+ recipes
+ id (PK)
+ title
+ description
+ instructions
+ image_url
+ avg_rating
+
+
+
+
+
+
+ kitchen (join)
+ id (PK)
+ user_id (FK)
+ ingredient_id (FK)
+ quantity · unit
+
+
+
+
+
+
+ recipe_ingredients (join)
+ id (PK)
+ recipe_id (FK)
+ ingredient_id (FK)
+ quantity · unit
+
+
+
+
+
+
+ favorites (join)
+ id (PK)
+ user_id (FK)
+ recipe_id (FK)
+
+
+
+
+
+
+ ratings (join)
+ id (PK)
+ user_id (FK)
+ recipe_id (FK)
+ stars
+
+
+
+
+
+
+ scheduled_meals (join)
+ id (PK) · user_id (FK) · recipe_id (FK)
+ date · meal_type
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ∞
+ ∞
+ ∞
+ ∞
+ ∞
+ ∞
+ ∞
+ ∞
+ ∞
+ ∞
+
+
+ Blue = user-facing entities · Green = catalog · Orange = recipes · Purple = join tables (resolve many-to-many)
+
diff --git a/planning/user_stories.md b/planning/user_stories.md
index 1e55ecbcd..2c6b1b852 100644
--- a/planning/user_stories.md
+++ b/planning/user_stories.md
@@ -4,10 +4,22 @@ Reference the Writing User Stories final project guide in the course portal for
## Outline User Roles
-[👉🏾👉🏾👉🏾 Include at least at least 1, but no more than 3, user roles.]
+- Parent
+- Student
+- Home cook
## Draft User Stories
-[👉🏾👉🏾👉🏾 Include at least at least 10 user stories in this format:]
+1. As a guest, I want to browse available recipes so that I can explore what the app offers before creating an account.
+2. As a registered user, I want to create an account and log in so that I can save my personal information and preferences.
+3. As a registered user, I want to view all available recipes so that I can discover new meal ideas.
+4. As a registered user, I want to sort recipes by rating, newest, or favorites so that I can quickly find recipes I'm interested in.
+5. As a registered user, I want to maintain a list of ingredients in my kitchen so that I can keep track of what I have available.
+6. As a registered user, I want to add new ingredients to my kitchen so that my available ingredients stay up to date.
+7. As a registered user, I want to view nutritional and dietary information for ingredients so that I can make informed meal choices.
+8. As a registered user, I want to edit my dietary preferences in my profile so that recipe recommendations match my dietary needs.
+9. As a registered user, I want to access my kitchen from my profile so that I can easily manage my ingredients.
+10. As a registered user, I want to schedule meals on a calendar so that I can plan my meals in advance.
+11. As a registered user, I want to record the meals I have eaten so that I can keep track of my meal history.
+12. As an administrator, I want to manage recipes and user-generated content so that the application remains accurate and appropriate.
-1. As a [user role], I want to [what], so that [why].
diff --git a/planning/wireframes.md b/planning/wireframes.md
index fbcd15a0c..9e7b5b14b 100644
--- a/planning/wireframes.md
+++ b/planning/wireframes.md
@@ -4,18 +4,33 @@ Reference the Creating an Entity Relationship Diagram final project guide in the
## List of Pages
-[👉🏾👉🏾👉🏾 List the pages you expect to have in your app, with a ⭐ next to pages you have wireframed]
+- ⭐ Home / Recipe Results
+- ⭐ My Kitchen
+- ⭐ Add Ingredient
+- ⭐ User Profile
+- ⭐ Log In / Sign Up
+- ⭐ Meal Calendar
-## Wireframe 1: [page title]
+## Wireframe 1: Home / Recipe Results
-[👉🏾👉🏾👉🏾 include wireframe 1]
+
-## Wireframe 2: [page title]
+## Wireframe 2: My Kitchen
-[👉🏾👉🏾👉🏾 include wireframe 2]
+
-## Wireframe 3: [page title]
+## Wireframe 3: Add Ingredient
-[👉🏾👉🏾👉🏾 include wireframe 3]
+
-[👉🏾👉🏾👉🏾 include more wireframes as desired]
+## Wireframe 4: User Profile
+
+
+
+## Wireframe 5: Log In / Sign Up
+
+
+
+## Wireframe 6: Meal Calendar
+
+
diff --git a/planning/wireframes/calendar.svg b/planning/wireframes/calendar.svg
new file mode 100644
index 000000000..f0ccb13ce
--- /dev/null
+++ b/planning/wireframes/calendar.svg
@@ -0,0 +1,62 @@
+
+
+
+
+
+ EatRite
+ Home
+ My Kitchen
+ Calendar
+ Profile
+
+ Log Out
+
+ Meal Calendar
+ ◀
+ July 2026
+ ▶
+
+
+
+ + Schedule Meal
+
+
+
+ Sun
+ Mon
+ Tue
+ Wed
+ Thu
+ Fri
+ Sat
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+ 2
+
+ Lunch: Pasta
+
+ 3
+ 4
+
+ Dinner: Tacos
+
+ 5
+ 6
+ 7
+
diff --git a/planning/wireframes/home.svg b/planning/wireframes/home.svg
new file mode 100644
index 000000000..4d3af2f97
--- /dev/null
+++ b/planning/wireframes/home.svg
@@ -0,0 +1,76 @@
+
+
+
+
+
+ EatRite
+ Home
+ My Kitchen
+ Calendar
+ Profile
+
+ Log Out
+
+
+ Recipe Results
+
+
+
+ Sort by: Rating ▾
+
+
+ Newest ▾
+
+
+ ♥ Favorites
+
+
+ Search recipes...
+
+
+
+
+
+
+ Recipe Title
+ ★★★★☆
+ ♥
+
+
+
+
+ Recipe Title
+ ★★★☆☆
+ ♥
+
+
+
+
+ Recipe Title
+ ★★★★★
+ ♥
+
+
+
+
+
+
+ Recipe Title
+ ★★☆☆☆
+ ♥
+
+
+
+
+ Recipe Title
+ ★★★★☆
+ ♥
+
+
+
+
+ Recipe Title
+ ★★★☆☆
+ ♥
+
+
diff --git a/planning/wireframes/ingredient_input.svg b/planning/wireframes/ingredient_input.svg
new file mode 100644
index 000000000..c3f045266
--- /dev/null
+++ b/planning/wireframes/ingredient_input.svg
@@ -0,0 +1,45 @@
+
+
+
+
+
+ EatRite
+ Home
+ My Kitchen
+ Calendar
+ Profile
+
+ Log Out
+
+ My Kitchen >
+ Add Ingredient
+
+ Add Ingredient
+
+
+
+
+ Ingredient Name
+
+
+ Category
+
+ Select category ▾
+
+ Quantity
+
+
+ Unit
+
+ Select unit ▾
+
+ Nutritional / Dietary Info
+
+ calories, allergens, tags...
+
+
+ Save
+
+
+ Cancel
+
diff --git a/planning/wireframes/kitchen.svg b/planning/wireframes/kitchen.svg
new file mode 100644
index 000000000..6c7b5da3a
--- /dev/null
+++ b/planning/wireframes/kitchen.svg
@@ -0,0 +1,65 @@
+
+
+
+
+
+ EatRite
+ Home
+ My Kitchen
+ Calendar
+ Profile
+
+ Log Out
+
+
+ My Kitchen
+
+ + Add Ingredient
+
+
+
+
+
+ Ingredient Name
+ Qty: 2 units
+ Category: Produce
+
+
+
+
+ Ingredient Name
+ Qty: 1 unit
+ Category: Dairy
+
+
+
+
+ Ingredient Name
+ Qty: 3 units
+ Category: Grain
+
+
+
+
+
+ Ingredient Name
+ Qty: 4 units
+ Category: Protein
+
+
+
+
+ Ingredient Name
+ Qty: 2 units
+ Category: Spice
+
+
+
+
+ Ingredient Name
+ Qty: 1 unit
+ Category: Produce
+
+
+ (scroll for more ingredients)
+
diff --git a/planning/wireframes/login.svg b/planning/wireframes/login.svg
new file mode 100644
index 000000000..cd2d9a658
--- /dev/null
+++ b/planning/wireframes/login.svg
@@ -0,0 +1,25 @@
+
+
+
+ EatRite
+
+
+
+ Log In
+
+ Email
+
+
+ Password
+
+
+
+ Log In
+
+ Forgot password?
+
+
+
+ Don't have an account?
+ Sign Up
+
diff --git a/planning/wireframes/profile.svg b/planning/wireframes/profile.svg
new file mode 100644
index 000000000..7b22ad453
--- /dev/null
+++ b/planning/wireframes/profile.svg
@@ -0,0 +1,55 @@
+
+
+
+
+
+ EatRite
+ Home
+ My Kitchen
+ Calendar
+ Profile
+
+ Log Out
+
+
+
+ Jane Doe
+ jane.doe@email.com
+
+ Edit Profile
+
+
+
+ Dietary Preferences
+
+ Edit Preferences
+
+ Vegetarian
+
+ Low Sugar
+
+ Nut-Free
+
+
+
+ My Kitchen
+ View →
+
+
+ Favorite Recipes
+
+
+
+ Recipe Title
+
+
+
+
+ Recipe Title
+
+
+
+
+ Recipe Title
+
+
diff --git a/server/config/auth.js b/server/config/auth.js
new file mode 100644
index 000000000..caf552763
--- /dev/null
+++ b/server/config/auth.js
@@ -0,0 +1,46 @@
+import { pool } from '../db/dbpool.js'
+// import options from '../config/auth.js'
+import '../config/dotenv.js'
+import GitHubStrategy from "passport-github2";
+
+const options = {
+ clientID: process.env.GITHUB_CLIENT_ID,
+ clientSecret: process.env.GITHUB_CLIENT_SECRET,
+ callbackURL: 'http://localhost:3000/auth/github/callback'
+}
+
+
+const verify = async (accessToken, refreshToken, profile, callback) => {
+ const { _json: { id, login, avatar_url } } = profile
+
+ const userData = {
+ githubId: id,
+ username: login,
+ avatarUrl: avatar_url,
+ accessToken
+ }
+
+ try {
+ const results = await pool.query(`
+ SELECT * FROM users WHERE githubid=$1`, [userData.githubId])
+ const user = results.rows[0]
+ if (!user) {
+ const results = await pool.query(`
+ INSERT INTO users (githubid, username, avatarurl, accesstoken)
+ VALUES($1, $2, $3, $4)
+ RETURNING *`,
+ [userData.githubId, userData.username, userData.avatarUrl, accessToken])
+
+ const newUser = results.rows[0]
+ return callback(null, newUser)
+ }
+
+ return callback(null, user)
+ } catch (err) {
+ return callback(err)
+ }
+}
+
+export const GitHub = new GitHubStrategy(options, verify)
+
+export default options
diff --git a/server/config/dotenv.js b/server/config/dotenv.js
new file mode 100644
index 000000000..ce36c95bd
--- /dev/null
+++ b/server/config/dotenv.js
@@ -0,0 +1,6 @@
+import dotenv from 'dotenv'
+
+// give dotenv the envronment variables
+dotenv.config({
+ path:'./.env'
+})
diff --git a/server/controllers/favoritesController.js b/server/controllers/favoritesController.js
new file mode 100644
index 000000000..edfd00037
--- /dev/null
+++ b/server/controllers/favoritesController.js
@@ -0,0 +1,58 @@
+import model from '../models/favoritesModel.js'
+
+const getFavorites = async (req, res) => {
+ try {
+ const response = await model.getFavorites(req, res)
+ res.status(200).json(response)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const getFavorite = async (req, res) => {
+ try {
+ const response = await model.getFavorites(req, res)
+ res.status(200).json(response)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const createFavorite = async (req, res) => {
+ try {
+ const response = await model.createFavorite(req, res)
+ res.status(200).json(response)
+ console.log('controller creating favorite')
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+
+// const updateFavorite = async (req, res) => {
+// try {
+// const response = await model.getFavorites(req, res)
+// res.status(200).json(response)
+// } catch (err) {
+// res.status(500).json({ message: err.message })
+// }
+// }
+
+
+const deleteFavorite = async (req, res) => {
+ try {
+ console.log('del fav controller reached')
+ const response = await model.deleteFavorite(req, res)
+ res.status(200).json(response)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+
+export default {
+ getFavorites,
+ getFavorite,
+ createFavorite,
+ deleteFavorite,
+}
diff --git a/server/controllers/ingredientsController.js b/server/controllers/ingredientsController.js
new file mode 100644
index 000000000..1f4020c42
--- /dev/null
+++ b/server/controllers/ingredientsController.js
@@ -0,0 +1,47 @@
+import models from '../models/ingredientsModel.js'
+
+const createIngredient = async (req, res) => {
+ // console.log("creteIngredient ++")
+ try {
+ const newIngredient = await models.createIngredient(req.body)
+ res.status(201).json(newIngredient)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const getIngredients = async (req, res) => {
+ try {
+ const ingredients = await models.getIngredients(req, res)
+ res.status(200).json(ingredients)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const getIngredient = async (req, res) => {
+ try {
+ const ingredient = await models.getIngredient(req)
+ res.status(200).json(ingredient)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const updateIngredient = async (req, res) => {
+ try {
+ const id = req.params.id
+ const data = req.body
+ const updatedIngredient = await models.updateIngredient(id, data)
+ res.status(201).json(updatedIngredient)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+export default {
+ createIngredient,
+ getIngredients,
+ getIngredient,
+ updateIngredient,
+}
diff --git a/server/controllers/kitchenController.js b/server/controllers/kitchenController.js
new file mode 100644
index 000000000..6747a30c7
--- /dev/null
+++ b/server/controllers/kitchenController.js
@@ -0,0 +1,34 @@
+import model from '../models/kitchenModel.js'
+
+const getKitchen = async (req, res) => {
+ try {
+ const results = await model.getKitchen(req, res)
+ res.status(200).json(results)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const addToKitchen = async (req, res) => {
+ try {
+ const result = await model.addToKitchen(req, res)
+ res.status(201).json(result)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const removeFromKitchen = async (req, res) => {
+ try {
+ const result = await model.removeFromKitchen(req, res)
+ res.status(200).json(result)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+export default {
+ getKitchen,
+ addToKitchen,
+ removeFromKitchen,
+}
diff --git a/server/controllers/preferenceController.js b/server/controllers/preferenceController.js
new file mode 100644
index 000000000..21acb57aa
--- /dev/null
+++ b/server/controllers/preferenceController.js
@@ -0,0 +1,46 @@
+import model from '../models/preferenceModel.js'
+
+const createPreference = async (req, res) => {
+ try {
+ const response = await model.createPreference(req, res)
+ res.status(200).json(response)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const getPreferences = async (req, res) => {
+ try {
+ const response = await model.getPreferences(req, res)
+ res.status(200).json(response)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const getPreference = async (req, res) => {
+ try {
+ const response = await model.getPreference(req, res)
+ res.status(200).json(response)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const deletePreference = async (req, res) => {
+ try {
+ const response = await model.deletePreference(req, res)
+ res.status(200).json(response)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+export default {
+ createPreference,
+ getPreferences,
+ getPreference,
+ deletePreference,
+}
+
+
diff --git a/server/controllers/recipeController.js b/server/controllers/recipeController.js
new file mode 100644
index 000000000..9ebbf56d5
--- /dev/null
+++ b/server/controllers/recipeController.js
@@ -0,0 +1,55 @@
+import model from '../models/recipeModel.js'
+
+const createRecipe = async (req, res) => {
+ try {
+ const newRecipe = await model.createRecipe(req, res)
+ res.status(201).json(newRecipe)
+
+ } catch (err) {
+ res.status(500).json({ message: err.message})
+ }
+}
+
+const getRecipes = async (req, res) => {
+ try {
+ const results = await model.getRecipes(req, res)
+ res.status(201).json(results)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const getRecipe = async (req, res) => {
+ try {
+ const data = await model.getRecipe(req, res)
+ res.status(200).json(data)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const updateRecipe = async (req, res) => {
+ try {
+ const result = await model.updateRecipe(req, res)
+ res.status(201).json(result)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const deleteRecipe = async (req, res) => {
+ try {
+ const result = await model.deleteRecipe(req, res)
+ res.status(200).json(result)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+export default {
+ createRecipe,
+ getRecipes,
+ getRecipe,
+ updateRecipe,
+ deleteRecipe,
+}
diff --git a/server/controllers/scheduledMealsController.js b/server/controllers/scheduledMealsController.js
new file mode 100644
index 000000000..85c237243
--- /dev/null
+++ b/server/controllers/scheduledMealsController.js
@@ -0,0 +1,34 @@
+import model from '../models/scheduledMealsModel.js'
+
+const getScheduledMeals = async (req, res) => {
+ try {
+ const results = await model.getScheduledMeals(req, res)
+ res.status(200).json(results)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const createScheduledMeal = async (req, res) => {
+ try {
+ const result = await model.createScheduledMeal(req, res)
+ res.status(201).json(result)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const deleteScheduledMeal = async (req, res) => {
+ try {
+ const result = await model.deleteScheduledMeal(req, res)
+ res.status(200).json(result)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+export default {
+ getScheduledMeals,
+ createScheduledMeal,
+ deleteScheduledMeal,
+}
diff --git a/server/controllers/userController.js b/server/controllers/userController.js
new file mode 100644
index 000000000..44809db97
--- /dev/null
+++ b/server/controllers/userController.js
@@ -0,0 +1,65 @@
+import model from '../models/userModel.js'
+
+const getUsers = async (req, res) => {
+ try {
+ const response = await model.getUsers(req, res)
+ res.status(200).json(response)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const getUser = async (req, res) => {
+ try {
+ const response = await model.getUser(req, res)
+ res.status(200).json(response)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const createUser = async (req, res) => {
+ try {
+ const response = model.getUser(req, res)
+ res.status(201).json(response)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const updateUser = async (req, res) => {
+ try {
+ const response = await model.updateUser(req, res)
+ res.status(200).json(response)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const deleteUser = async (req, res) => {
+ try {
+ const response = await model.deleteUser(req, res)
+ res.status(200).json(response)
+ } catch (err) {
+ res.status(500).json({ message: err.message })
+ }
+}
+
+const createPreference = async (req, res) => {
+ try {
+ const response = await model.updateDietaryPreferences(req, res)
+ res.status(200).json(response)
+ } catch (err) {
+ return res.status(500).json({ message: err.message })
+ }
+}
+
+
+export default {
+ getUsers,
+ getUser,
+ createUser,
+ updateUser,
+ deleteUser,
+ createPreference,
+}
diff --git a/server/db/dbpool.js b/server/db/dbpool.js
new file mode 100644
index 000000000..a553b53b2
--- /dev/null
+++ b/server/db/dbpool.js
@@ -0,0 +1,18 @@
+import pg from 'pg'
+import '../config/dotenv.js'
+
+
+console.log(`USER: ${process.env.PGUSER}`)
+
+const config = {
+ user: process.env.PGUSER,
+ password: process.env.PGPASSWORD,
+ host: process.env.PGHOST,
+ port: process.env.PGPORT,
+ database: process.env.PGDATABASE,
+ ssl: {
+ rejectUnauthorized: false
+ }
+}
+
+export const pool = new pg.Pool(config)
diff --git a/server/db/schema.js b/server/db/schema.js
new file mode 100644
index 000000000..7db8fd36e
--- /dev/null
+++ b/server/db/schema.js
@@ -0,0 +1,157 @@
+import { pool } from './dbpool.js'
+
+const createTable = async () => {
+ const query = `
+
+ drop table if exists scheduled_meals;
+ DROP TABLE IF EXISTS ratings;
+ DROP TABLE IF EXISTS favorites;
+ DROP TABLE IF EXISTS recipe_ingredients;
+ DROP TABLE IF EXISTS kitchen;
+ DROP TABLE IF EXISTS dietary_preferences;
+ DROP TABLE IF EXISTS recipes;
+ DROP TABLE IF EXISTS ingredients;
+ DROP TABLE IF EXISTS users;
+
+ -- CREATE TABLE IF NOT EXISTS users (
+ -- id SERIAL PRIMARY KEY,
+ -- name TEXT NOT NULL,
+ -- email TEXT UNIQUE NOT NULL,
+ -- password_hash TEXT NOT NULL,
+ -- dietary_preferences TEXT
+ -- );
+
+ CREATE TABLE IF NOT EXISTS users (
+ id serial PRIMARY KEY,
+ githubid integer NOT NULL,
+ username varchar(100) NOT NULL,
+ avatarurl varchar(500) NOT NULL,
+ accesstoken varchar(500) NOT NULL,
+ email TEXT UNIQUE,
+ is_admin BOOLEAN NOT NULL DEFAULT false
+ );
+
+ CREATE TABLE IF NOT EXISTS dietary_preferences (
+ id serial PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ preference varchar(500) NOT NULL,
+
+ FOREIGN KEY (user_id)
+ REFERENCES users(id)
+ ON DELETE CASCADE
+ );
+
+ CREATE TABLE ingredients (
+ id SERIAL PRIMARY KEY,
+ name TEXT NOT NULL,
+ category TEXT,
+ calories INTEGER,
+ dietary_tags TEXT
+ );
+
+
+ CREATE TABLE recipes (
+ id SERIAL PRIMARY KEY,
+ title TEXT NOT NULL,
+ description TEXT,
+ instructions TEXT,
+ image_url TEXT,
+ avg_rating NUMERIC(3,2) DEFAULT 0
+ );
+
+
+ CREATE TABLE kitchen (
+ id SERIAL PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ ingredient_id INTEGER NOT NULL,
+ quantity NUMERIC(10,2),
+ unit TEXT,
+
+ FOREIGN KEY (user_id)
+ REFERENCES users(id)
+ ON DELETE CASCADE,
+
+ FOREIGN KEY (ingredient_id)
+ REFERENCES ingredients(id)
+ ON DELETE CASCADE
+ );
+
+
+ CREATE TABLE recipe_ingredients (
+ id SERIAL PRIMARY KEY,
+ recipe_id INTEGER NOT NULL,
+ ingredient_id INTEGER NOT NULL,
+ quantity NUMERIC(10,2),
+ unit TEXT,
+
+ FOREIGN KEY (recipe_id)
+ REFERENCES recipes(id)
+ ON DELETE CASCADE,
+
+ FOREIGN KEY (ingredient_id)
+ REFERENCES ingredients(id)
+ ON DELETE CASCADE
+ );
+
+
+ CREATE TABLE favorites (
+ id SERIAL PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ recipe_id INTEGER NOT NULL,
+
+ FOREIGN KEY (user_id)
+ REFERENCES users(id)
+ ON DELETE CASCADE,
+
+ FOREIGN KEY (recipe_id)
+ REFERENCES recipes(id)
+ ON DELETE CASCADE,
+
+ UNIQUE(user_id, recipe_id)
+ );
+
+
+ CREATE TABLE ratings (
+ id SERIAL PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ recipe_id INTEGER NOT NULL,
+ stars INTEGER NOT NULL,
+
+ CHECK (stars BETWEEN 1 AND 5),
+
+ FOREIGN KEY (user_id)
+ REFERENCES users(id)
+ ON DELETE CASCADE,
+
+ FOREIGN KEY (recipe_id)
+ REFERENCES recipes(id)
+ ON DELETE CASCADE,
+
+ UNIQUE(user_id, recipe_id)
+ );
+
+
+ CREATE TABLE scheduled_meals (
+ id SERIAL PRIMARY KEY,
+ user_id INTEGER NOT NULL,
+ recipe_id INTEGER NOT NULL,
+ date DATE NOT NULL,
+ meal_type TEXT,
+
+ FOREIGN KEY (user_id)
+ REFERENCES users(id)
+ ON DELETE CASCADE,
+
+ FOREIGN KEY (recipe_id)
+ REFERENCES recipes(id)
+ ON DELETE CASCADE
+ );
+ `
+
+ await pool.query(query)
+
+ console.log('EatRite tables created')
+}
+
+
+createTable()
diff --git a/server/middleware/requireAuth.js b/server/middleware/requireAuth.js
new file mode 100644
index 000000000..511576532
--- /dev/null
+++ b/server/middleware/requireAuth.js
@@ -0,0 +1,13 @@
+export const requireAuth = (req, res, next) => {
+ if (!req.user) {
+ return res.status(401).json({ message: 'You must be logged in' })
+ }
+ next()
+}
+
+export const requireAdmin = (req, res, next) => {
+ if (!req.user?.is_admin) {
+ return res.status(403).json({ message: 'Admin access required' })
+ }
+ next()
+}
diff --git a/server/middleware/verify.js b/server/middleware/verify.js
new file mode 100644
index 000000000..bc049b81a
--- /dev/null
+++ b/server/middleware/verify.js
@@ -0,0 +1,46 @@
+// import pool from '../db/dbpool.js'
+// import options from '../config/auth.js'
+// import '../config/dotenv.js'
+//
+//
+// const verify = (accessToken, refreshToken, profile, callback) => {
+// const { _json: { id, login, avatar_url } } = profile
+//
+//
+// console.log(profile)
+//
+// const userData = {
+// githubId: id,
+// username: login,
+// avatarUrl: avatar_url,
+// accessToken
+// }
+//
+// console.log("USER DATA:")
+// console.log("githubId:", userData.githubId)
+// console.log("username:", userData.username)
+// console.log("avatarUrl:", userData.avatarUrl)
+// console.log("accessToken:", userData.accessToken)
+//
+// try {
+// const results = await pool.query(`
+// SELECT * FROM users WHERE githubid=$1`, [userData.githubId])
+// const user = results.rows[0]
+// if (!user) {
+// const results = await.pool.query(`
+// INSERT INTO users (githubid, username, avatarurl, accesstoken)
+// VALUES($1, $2, $3, $4)
+// RETURNING *`,
+// [userData.githubId, userData.username, userData.avatarUrl, accessToken])
+//
+// const newUser = results.rows[0]
+// return callback(null, newUser)
+// }
+//
+// reutrn ballback(null, user)
+// } catch {
+// return callback(error)
+// }
+// }
+//
+// export const GitHub = new GitHubStrategy(options, verify)
diff --git a/server/models/favoritesModel.js b/server/models/favoritesModel.js
new file mode 100644
index 000000000..fd5ae5bf8
--- /dev/null
+++ b/server/models/favoritesModel.js
@@ -0,0 +1,53 @@
+import { pool } from '../db/dbpool.js'
+
+const getFavorites = async (req, res) => {
+ const userId = req.user.id
+ // console.log("USER ID:", userId)
+ const result = await pool.query(`
+ SELECT * FROM favorites
+ WHERE user_id=$1`, [userId])
+ // console.log("ROWS:", result.rows)
+ return result.rows
+}
+
+const getFavorite = async (req, res) => {
+ const { id } = req.params
+ const result = await pool.query(`
+ SELECT * FROM favorites
+ WHERE id=$1`, [id])
+ return result.rows[0]
+}
+
+const createFavorite = async (req, res) => {
+ const { recipe_id } = req.body
+ const userId = req.user.id
+ const result = await pool.query(`
+ INSERT INTO favorites (user_id, recipe_id)
+ VALUES ($1, $2)
+ RETURNING *`, [userId, recipe_id])
+ // console.log(`model creating favorite ${result.rows[0]}`)
+ return result.rows[0]
+}
+
+const deleteFavorite = async (req, res) => {
+ const { id } = req.params
+ const userId = req.user.id
+ console.log(`recipeID: ${id}`)
+ console.log(`userID: ${userId}`)
+ const result = await pool.query(`
+ DELETE FROM favorites
+ WHERE user_id=$1 AND recipe_id=$2
+ RETURNING *`, [userId,id])
+ console.log(`RESULT: ${result}`)
+ return result.rows[0]
+}
+
+export default {
+ getFavorites,
+ getFavorite,
+ createFavorite,
+ deleteFavorite,
+}
+
+
+
diff --git a/server/models/ingredientsModel.js b/server/models/ingredientsModel.js
new file mode 100644
index 000000000..7f83aad94
--- /dev/null
+++ b/server/models/ingredientsModel.js
@@ -0,0 +1,45 @@
+import { pool } from '../db/dbpool.js'
+
+const createIngredient = async (ingredient) => {
+ const result = await pool.query(`
+ INSERT INTO ingredients (name, category, calories, dietary_tags)
+ VALUES ($1, $2, $3, $4)
+ RETURNING *`,
+ [ingredient.name, ingredient.category, ingredient.calories, ingredient.dietary_tags]
+ );
+ return result.rows[0];
+}
+
+const getIngredients = async () => {
+ const result = await pool.query(`
+ SELECT * FROM ingredients
+`);
+ return result.rows;
+}
+
+const getIngredient = async (req, res) => {
+ const { id } = req.params;
+ const result = await pool.query(`
+ SELECT * FROM ingredients WHERE id = $1`, [id]
+ );
+ return result.rows[0];
+}
+
+const updateIngredient = async (id, data) => {
+ const { name, category, calories, dietary_tags } = data
+ const result = await pool.query(`
+ UPDATE ingredients
+ SET name=$1, category=$2, calories=$3, dietary_tags=$4
+ WHERE id=$5
+ RETURNING *`,
+ [name, category, calories, dietary_tags, id]
+ );
+ return result.rows[0];
+}
+
+export default {
+ createIngredient,
+ getIngredients,
+ getIngredient,
+ updateIngredient,
+}
diff --git a/server/models/kitchenModel.js b/server/models/kitchenModel.js
new file mode 100644
index 000000000..80649521a
--- /dev/null
+++ b/server/models/kitchenModel.js
@@ -0,0 +1,64 @@
+import { pool } from '../db/dbpool.js'
+import ingredientsModel from './ingredientsModel.js'
+
+const getKitchen = async (req, res) => {
+ const userId = req.user.id
+ const result = await pool.query(`
+ SELECT kitchen.id, kitchen.quantity, kitchen.unit,
+ ingredients.id AS ingredient_id, ingredients.name,
+ ingredients.category, ingredients.calories, ingredients.dietary_tags
+ FROM kitchen
+ JOIN ingredients ON ingredients.id = kitchen.ingredient_id
+ WHERE kitchen.user_id=$1`, [userId])
+ return result.rows
+}
+
+const updateQuantity = async (userId, ingredientId, quantity, unit) => {
+ const result = await pool.query(`
+ UPDATE kitchen SET quantity=$1, unit=$2
+ WHERE user_id=$3 AND ingredient_id=$4
+ RETURNING *`, [quantity || null, unit || null, userId, ingredientId])
+ return result.rows[0]
+}
+
+const addToKitchen = async (req, res) => {
+ const userId = req.user.id
+ const { name, category, calories, dietary_tags, quantity, unit } = req.body
+
+ const existing = await pool.query(`
+ SELECT * FROM ingredients WHERE name=$1`, [name])
+ const ingredient = existing.rows[0]
+ || await ingredientsModel.createIngredient({ name, category, calories, dietary_tags })
+
+ const alreadyInKitchen = await pool.query(`
+ SELECT * FROM kitchen
+ WHERE user_id=$1 AND ingredient_id=$2`, [userId, ingredient.id])
+
+ if (alreadyInKitchen.rows[0]) {
+ const updated = await updateQuantity(userId, ingredient.id, quantity, unit)
+ return { ...ingredient, ...updated }
+ }
+
+ const kitchenResult = await pool.query(`
+ INSERT INTO kitchen (user_id, ingredient_id, quantity, unit)
+ VALUES ($1, $2, $3, $4)
+ RETURNING *`, [userId, ingredient.id, quantity || null, unit || null])
+
+ return { ...ingredient, ...kitchenResult.rows[0] }
+}
+
+const removeFromKitchen = async (req, res) => {
+ const { ingredientId } = req.params
+ const userId = req.user.id
+ const result = await pool.query(`
+ DELETE FROM kitchen
+ WHERE user_id=$1 AND ingredient_id=$2
+ RETURNING *`, [userId, ingredientId])
+ return result.rows[0]
+}
+
+export default {
+ getKitchen,
+ addToKitchen,
+ removeFromKitchen,
+}
diff --git a/server/models/preferenceModel.js b/server/models/preferenceModel.js
new file mode 100644
index 000000000..d86204ff4
--- /dev/null
+++ b/server/models/preferenceModel.js
@@ -0,0 +1,48 @@
+import { pool } from '../db/dbpool.js'
+
+const createPreference = async (req, res) => {
+ const userId = req.user.id
+ const { preference } = req.body
+ const result = await pool.query(`
+ INSERT INTO dietary_preferences
+ (user_id, preference)
+ VALUES ($1, $2)
+ RETURNING *`, [userId, preference])
+ return result.rows[0]
+}
+
+const getPreference = async (req, res) => {
+ const { id } = req.params
+ const userId = req.user.id
+ console.log("USER:", req.user)
+ console.log("BODY:", req.body)
+ const result = await pool.query(`
+ SELECT * FROM dietary_preferences
+ WHERE id=$1
+ `, [id])
+ return result.rows[0]
+}
+
+const getPreferences = async (req, res) => {
+ // const { user_id } = req.body
+ const userId = req.user.id
+ const result = await pool.query(`
+ SELECT * FROM dietary_preferences
+ WHERE user_id=$1`, [userId])
+ return result.rows
+}
+
+const deletePreference = async (req, res) => {
+ const { id } = req.params
+ const result = await pool.query(`
+ DELETE FROM dietary_preferences
+ WHERE id=$1 RETURNING *`, [id])
+ return result.rows[0]
+}
+
+export default {
+ createPreference,
+ getPreference,
+ getPreferences,
+ deletePreference,
+}
diff --git a/server/models/recipeModel.js b/server/models/recipeModel.js
new file mode 100644
index 000000000..be40719c9
--- /dev/null
+++ b/server/models/recipeModel.js
@@ -0,0 +1,59 @@
+import { pool } from '../db/dbpool.js'
+
+const createRecipe = async (req, res) => {
+ const { id, title, description, instructions, image_url, avg_rating } = req.body
+
+ const result = await pool.query(
+ `
+ INSERT INTO recipes
+ (title, description, instructions, image_url, avg_rating)
+ VALUES ($1, $2, $3, $4, $5)
+ RETURNING *
+ `,
+ [title, description, instructions, image_url, avg_rating]
+ )
+ return result.rows[0]
+}
+
+const getRecipes = async (req, res) => {
+ const query = `SELECT * FROM recipes`
+ const data = await pool.query(query)
+ return data.rows
+}
+
+const getRecipe = async (req, res) => {
+ const { id } = req.params
+ const query = `SELECT * FROM recipes WHERE id=$1`
+ const data = await pool.query(query, [id])
+ return data.rows[0]
+}
+
+const updateRecipe = async (req, res) => {
+ const { id } = req.params
+ const data = req.body
+ const { title, description, instructions, image_url, avg_rating } = data
+ const result = await pool.query(`
+ UPDATE recipes
+ SET title=$1, description=$2, instructions=$3, image_url=$4, avg_rating=$5
+ RETURNING *`,
+ [title, description, instructions, image_url, avg_rating]
+ );
+ return result.rows[0]
+}
+
+const deleteRecipe = async (req, res) => {
+ const { id } = req.params
+ const result = await pool.query(`
+ DELETE FROM recipes WHERE id=$1
+ RETURNING *`, [id]
+ );
+ return result.rows[0]
+}
+
+export default {
+ createRecipe,
+ getRecipes,
+ getRecipe,
+ updateRecipe,
+ deleteRecipe
+}
diff --git a/server/models/scheduledMealsModel.js b/server/models/scheduledMealsModel.js
new file mode 100644
index 000000000..abb2e3885
--- /dev/null
+++ b/server/models/scheduledMealsModel.js
@@ -0,0 +1,38 @@
+import { pool } from '../db/dbpool.js'
+
+const getScheduledMeals = async (req, res) => {
+ const userId = req.user.id
+ const result = await pool.query(`
+ SELECT scheduled_meals.*, recipes.title, recipes.image_url
+ FROM scheduled_meals
+ JOIN recipes ON recipes.id = scheduled_meals.recipe_id
+ WHERE scheduled_meals.user_id=$1
+ ORDER BY scheduled_meals.date ASC`, [userId])
+ return result.rows
+}
+
+const createScheduledMeal = async (req, res) => {
+ const userId = req.user.id
+ const { recipe_id, date, meal_type } = req.body
+ const result = await pool.query(`
+ INSERT INTO scheduled_meals (user_id, recipe_id, date, meal_type)
+ VALUES ($1, $2, $3, $4)
+ RETURNING *`, [userId, recipe_id, date, meal_type])
+ return result.rows[0]
+}
+
+const deleteScheduledMeal = async (req, res) => {
+ const { id } = req.params
+ const userId = req.user.id
+ const result = await pool.query(`
+ DELETE FROM scheduled_meals
+ WHERE id=$1 AND user_id=$2
+ RETURNING *`, [id, userId])
+ return result.rows[0]
+}
+
+export default {
+ getScheduledMeals,
+ createScheduledMeal,
+ deleteScheduledMeal,
+}
diff --git a/server/models/userModel.js b/server/models/userModel.js
new file mode 100644
index 000000000..710a1f8cb
--- /dev/null
+++ b/server/models/userModel.js
@@ -0,0 +1,26 @@
+import { pool } from '../db/dbpool.js'
+
+const getUser = async (req, res) => {
+ const { id } = req.params
+ const result = await pool.query(`
+ SELECT * FROM users
+ WHERE id=$1`, [id])
+ return result.rows[0]
+}
+
+const updateDietaryPreferences = async (req, res) => {
+ const { id, dietary_preferences } = req.body
+ const result = await pool.query(`
+ UPDATE users
+ SET dietary_preferences = $1
+ WHERE id=$2 RETURNING *`, [dietary_preferences, id])
+ return result.rows[0]
+}
+
+export default {
+ getUser,
+ updateDietaryPreferences,
+}
+
+
+
diff --git a/server/package-lock.json b/server/package-lock.json
new file mode 100644
index 000000000..176478c3a
--- /dev/null
+++ b/server/package-lock.json
@@ -0,0 +1,1590 @@
+{
+ "name": "server",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "server",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "cors": "^2.8.6",
+ "dotenv": "^17.4.2",
+ "express": "^5.2.1",
+ "express-session": "^1.19.0",
+ "passport": "^0.7.0",
+ "passport-github2": "^0.1.12",
+ "pg": "^8.22.0"
+ },
+ "devDependencies": {
+ "nodemon": "^3.1.14"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/base64url": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz",
+ "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
+ "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^2.0.0",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.1",
+ "iconv-lite": "^0.7.2",
+ "on-finished": "^2.4.1",
+ "qs": "^6.15.2",
+ "raw-body": "^3.0.2",
+ "type-is": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/body-parser/node_modules/content-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "5.0.7",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
+ "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "17.4.2",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz",
+ "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express-session": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.19.0.tgz",
+ "integrity": "sha512-0csaMkGq+vaiZTmSMMGkfdCOabYv192VbytFypcvI0MANrp+4i/7yEkJ0sbAEhycQjntaKGzYfjfXQyVb7BHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "cookie": "~0.7.2",
+ "cookie-signature": "~1.0.7",
+ "debug": "~2.6.9",
+ "depd": "~2.0.0",
+ "on-headers": "~1.1.0",
+ "parseurl": "~1.3.3",
+ "safe-buffer": "~5.2.1",
+ "uid-safe": "~2.1.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express-session/node_modules/cookie-signature": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+ "license": "MIT"
+ },
+ "node_modules/express-session/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/express-session/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/fsevents": {
+ "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"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.7.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
+ "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/ignore-by-default": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/nodemon": {
+ "version": "3.1.14",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz",
+ "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^4",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^10.2.1",
+ "pstree.remy": "^1.1.8",
+ "semver": "^7.5.3",
+ "simple-update-notifier": "^2.0.0",
+ "supports-color": "^5.5.0",
+ "touch": "^3.1.0",
+ "undefsafe": "^2.0.5"
+ },
+ "bin": {
+ "nodemon": "bin/nodemon.js"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nodemon"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/oauth": {
+ "version": "0.10.2",
+ "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz",
+ "integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==",
+ "license": "MIT"
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/on-headers": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
+ "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/passport": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz",
+ "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "passport-strategy": "1.x.x",
+ "pause": "0.0.1",
+ "utils-merge": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/jaredhanson"
+ }
+ },
+ "node_modules/passport-github2": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/passport-github2/-/passport-github2-0.1.12.tgz",
+ "integrity": "sha512-3nPUCc7ttF/3HSP/k9sAXjz3SkGv5Nki84I05kSQPo01Jqq1NzJACgMblCK0fGcv9pKCG/KXU3AJRDGLqHLoIw==",
+ "dependencies": {
+ "passport-oauth2": "1.x.x"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/passport-oauth2": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz",
+ "integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==",
+ "license": "MIT",
+ "dependencies": {
+ "base64url": "3.x.x",
+ "oauth": "0.10.x",
+ "passport-strategy": "1.x.x",
+ "uid2": "0.0.x",
+ "utils-merge": "1.x.x"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/jaredhanson"
+ }
+ },
+ "node_modules/passport-strategy": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
+ "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/pause": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
+ "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
+ },
+ "node_modules/pg": {
+ "version": "8.22.0",
+ "resolved": "https://registry.npmjs.org/pg/-/pg-8.22.0.tgz",
+ "integrity": "sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA==",
+ "license": "MIT",
+ "dependencies": {
+ "pg-connection-string": "^2.14.0",
+ "pg-pool": "^3.14.0",
+ "pg-protocol": "^1.15.0",
+ "pg-types": "2.2.0",
+ "pgpass": "1.0.5"
+ },
+ "engines": {
+ "node": ">= 16.0.0"
+ },
+ "optionalDependencies": {
+ "pg-cloudflare": "^1.4.0"
+ },
+ "peerDependencies": {
+ "pg-native": ">=3.0.1"
+ },
+ "peerDependenciesMeta": {
+ "pg-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pg-cloudflare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz",
+ "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/pg-connection-string": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.0.tgz",
+ "integrity": "sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==",
+ "license": "MIT"
+ },
+ "node_modules/pg-int8": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/pg-pool": {
+ "version": "3.14.0",
+ "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz",
+ "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "pg": ">=8.0"
+ }
+ },
+ "node_modules/pg-protocol": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.15.0.tgz",
+ "integrity": "sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ==",
+ "license": "MIT"
+ },
+ "node_modules/pg-types": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+ "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+ "license": "MIT",
+ "dependencies": {
+ "pg-int8": "1.0.1",
+ "postgres-array": "~2.0.0",
+ "postgres-bytea": "~1.0.0",
+ "postgres-date": "~1.0.4",
+ "postgres-interval": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pgpass": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+ "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+ "license": "MIT",
+ "dependencies": {
+ "split2": "^4.1.0"
+ }
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postgres-array": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+ "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postgres-bytea": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
+ "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-date": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+ "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-interval": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+ "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "xtend": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/pstree.remy": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/qs": {
+ "version": "6.15.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
+ "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "es-define-property": "^1.0.1",
+ "side-channel": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/random-bytes": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
+ "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz",
+ "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "7.8.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4",
+ "side-channel-list": "^1.0.1",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/simple-update-notifier": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/split2": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/touch": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
+ "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "nodetouch": "bin/nodetouch.js"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
+ "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^2.0.0",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/type-is/node_modules/content-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/uid-safe": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
+ "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
+ "license": "MIT",
+ "dependencies": {
+ "random-bytes": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/uid2": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz",
+ "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==",
+ "license": "MIT"
+ },
+ "node_modules/undefsafe": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "license": "ISC"
+ },
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4"
+ }
+ }
+ }
+}
diff --git a/server/package.json b/server/package.json
new file mode 100644
index 000000000..0094e5d8a
--- /dev/null
+++ b/server/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "server",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "dev": "nodemon server.js"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "devDependencies": {
+ "nodemon": "^3.1.14"
+ },
+ "dependencies": {
+ "cors": "^2.8.6",
+ "dotenv": "^17.4.2",
+ "express": "^5.2.1",
+ "express-session": "^1.19.0",
+ "passport": "^0.7.0",
+ "passport-github2": "^0.1.12",
+ "pg": "^8.22.0"
+ }
+}
diff --git a/server/routes/auth.js b/server/routes/auth.js
new file mode 100644
index 000000000..80c970e3a
--- /dev/null
+++ b/server/routes/auth.js
@@ -0,0 +1,55 @@
+import express from 'express'
+import passport from 'passport'
+
+const router = express.Router()
+
+router.get('/login/success', (req, res) => {
+ if (req.user) {
+ res.status(200).json({ success: true, user: req.user })
+ }
+})
+
+router.get('/login/failed', (req, res) => {
+ res.status(401).json({ success: true, message: "failure" })
+})
+
+router.get('/logout', (req, res, next) => {
+ req.logout((err) => {
+ if (err) {
+ console.log('logout failed')
+ return next(err)
+ }
+ console.log('logout successful')
+ req.session.destroy((err) => {
+ res.clearCookie('connect.sid')
+ res.json({ status: "logout", user: {} })
+ })
+ })
+})
+
+router.get(
+ '/github',
+ passport.authenticate('github', {
+ scope: [ 'read:user' ]
+ })
+)
+
+// router.get(
+// '/github/callback',
+// passport.authenticate('github', {
+// successRedirect: '/',
+// failureRedirect:'/destinations',
+// })
+// )
+
+router.get(
+ '/github/callback',
+ passport.authenticate('github', {
+ failureRedirect: 'http://localhost:5173/',
+ }),
+ (req, res) => {
+ res.redirect('http://localhost:5173/home')
+ }
+)
+
+export default router
diff --git a/server/routes/favoritesRoutes.js b/server/routes/favoritesRoutes.js
new file mode 100644
index 000000000..96fdc9818
--- /dev/null
+++ b/server/routes/favoritesRoutes.js
@@ -0,0 +1,13 @@
+import express from 'express'
+import controller from '../controllers/favoritesController.js'
+import { requireAuth } from '../middleware/requireAuth.js'
+
+const routes = express.Router()
+
+routes.get(`/favorites`, requireAuth, controller.getFavorites)
+routes.get(`/favorite/:id`, requireAuth, controller.getFavorite)
+// routes.patch(`/favorite/:id`, controller.updateFavorite)
+routes.post(`/create/favorite`, requireAuth, controller.createFavorite)
+routes.delete(`/delete/favorite/:id`, requireAuth, controller.deleteFavorite)
+
+export default routes
diff --git a/server/routes/ingredientsRoutes.js b/server/routes/ingredientsRoutes.js
new file mode 100644
index 000000000..32639156a
--- /dev/null
+++ b/server/routes/ingredientsRoutes.js
@@ -0,0 +1,13 @@
+import express from 'express'
+// import controller from '../controllers/controller.js'
+import controller from '../controllers/ingredientsController.js'
+
+const router = express.Router()
+
+router.post("/create/ingredient", controller.createIngredient)
+router.get("/ingredients", controller.getIngredients)
+router.get("/ingredient/:id", controller.getIngredient)
+router.patch("/ingredient/:id", controller.updateIngredient)
+
+export default router
+
diff --git a/server/routes/kitchenRoutes.js b/server/routes/kitchenRoutes.js
new file mode 100644
index 000000000..bc092155d
--- /dev/null
+++ b/server/routes/kitchenRoutes.js
@@ -0,0 +1,11 @@
+import express from 'express'
+import controller from '../controllers/kitchenController.js'
+import { requireAuth } from '../middleware/requireAuth.js'
+
+const router = express.Router()
+
+router.get('/kitchen', requireAuth, controller.getKitchen)
+router.post('/create/kitchen-item', requireAuth, controller.addToKitchen)
+router.delete('/delete/kitchen-item/:ingredientId', requireAuth, controller.removeFromKitchen)
+
+export default router
diff --git a/server/routes/preferencesRoutes.js b/server/routes/preferencesRoutes.js
new file mode 100644
index 000000000..6934c63e8
--- /dev/null
+++ b/server/routes/preferencesRoutes.js
@@ -0,0 +1,12 @@
+import controller from '../controllers/preferenceController.js'
+import express from 'express'
+import { requireAuth } from '../middleware/requireAuth.js'
+
+const routes = express.Router()
+
+routes.post('/create/preference', requireAuth, controller.createPreference)
+routes.get('/preferences', requireAuth, controller.getPreferences)
+routes.get('/preference/:id', requireAuth, controller.getPreference)
+routes.delete('/delete/preference/:id', requireAuth, controller.deletePreference)
+
+export default routes
diff --git a/server/routes/recipeRoutes.js b/server/routes/recipeRoutes.js
new file mode 100644
index 000000000..a8ea70d02
--- /dev/null
+++ b/server/routes/recipeRoutes.js
@@ -0,0 +1,13 @@
+import express from 'express'
+import controller from '../controllers/recipeController.js'
+import { requireAdmin } from '../middleware/requireAuth.js'
+
+const router = express.Router()
+
+router.post("/create/recipe", requireAdmin, controller.createRecipe)
+router.get("/recipes", controller.getRecipes)
+router.get("/recipe/:id", controller.getRecipe)
+router.patch("/patch/recipe/:id", requireAdmin, controller.updateRecipe)
+router.delete("/delete/recipe/:id", requireAdmin, controller.deleteRecipe)
+
+export default router
diff --git a/server/routes/scheduledMealsRoutes.js b/server/routes/scheduledMealsRoutes.js
new file mode 100644
index 000000000..1f850eb5f
--- /dev/null
+++ b/server/routes/scheduledMealsRoutes.js
@@ -0,0 +1,11 @@
+import express from 'express'
+import controller from '../controllers/scheduledMealsController.js'
+import { requireAuth } from '../middleware/requireAuth.js'
+
+const router = express.Router()
+
+router.get('/scheduled-meals', requireAuth, controller.getScheduledMeals)
+router.post('/create/scheduled-meal', requireAuth, controller.createScheduledMeal)
+router.delete('/delete/scheduled-meal/:id', requireAuth, controller.deleteScheduledMeal)
+
+export default router
diff --git a/server/routes/userRoutes.js b/server/routes/userRoutes.js
new file mode 100644
index 000000000..235b6d973
--- /dev/null
+++ b/server/routes/userRoutes.js
@@ -0,0 +1,16 @@
+import controller from '../controllers/usercontroller.js'
+import express from 'express'
+
+const router = express.Router()
+
+// router.get('/users', controller.getusers)
+// router.get('/users/:id', controller.getuser)
+// router.post('/create/user', controller.createuser)
+// router.patch('/patch/user/:id', controller.updateuser)
+// router.delete('/delete/user/:id', controller.deleteuser)
+
+// router.patch('/create/preference', controller.createpreference)
+
+export default router
+
+
diff --git a/server/server.js b/server/server.js
new file mode 100644
index 000000000..4373bb49f
--- /dev/null
+++ b/server/server.js
@@ -0,0 +1,72 @@
+import express from 'express'
+import { pool } from './db/dbpool.js'
+import cors from 'cors'
+import IngredientsRoutes from './routes/ingredientsRoutes.js'
+import RecipeRoutes from './routes/recipeRoutes.js'
+import UserRoutes from './routes/userRoutes.js'
+import PreferenceRoutes from './routes/preferencesRoutes.js'
+import FavoriteRoutes from './routes/favoritesRoutes.js'
+import KitchenRoutes from './routes/kitchenRoutes.js'
+import ScheduledMealsRoutes from './routes/scheduledMealsRoutes.js'
+
+// authentication libraries
+import passport from 'passport'
+import session from 'express-session'
+import { GitHub } from './config/auth.js'
+import authRoutes from './routes/auth.js'
+
+const app = express();
+
+console.log("server setup")
+
+app.use(express.json())
+
+// auth
+
+
+app.use(cors({
+ origin: 'http://localhost:5173',
+ methods: 'GET, POST, PUT, DELETE, PATCH',
+ credentials: true
+}))
+
+app.use(session({
+ secret: 'codepath',
+ resave: false,
+ saveUninitialized: false
+}))
+
+app.use(passport.initialize())
+app.use(passport.session())
+passport.use(GitHub)
+passport.serializeUser((user,done) => {
+ done(null, user.id)
+})
+passport.deserializeUser(async (id, done) => {
+ try {
+ const result = await pool.query(`SELECT * FROM users WHERE id=$1`, [id])
+ done(null, result.rows[0])
+ } catch (err) {
+ done(err)
+ }
+})
+
+// routes
+app.use('/api', IngredientsRoutes)
+app.use('/api', RecipeRoutes)
+app.use('/api', UserRoutes)
+app.use('/api', PreferenceRoutes)
+app.use('/api', FavoriteRoutes)
+app.use('/api', KitchenRoutes)
+app.use('/api', ScheduledMealsRoutes)
+
+app.use('/auth', authRoutes)
+
+// app.get('/', function (req, res) {
+// res.send('hello, world!');
+// });
+
+app.listen(3000, function () {
+ console.log('Server started on port 3000');
+});
+