From 5590939cba1f482597cf5c112c4ee03226d034dc Mon Sep 17 00:00:00 2001 From: Meha Dave Date: Mon, 2 Feb 2026 01:16:21 -0800 Subject: [PATCH 1/2] Added Login and SignUp pages --- .gitignore | 4 + README.md | 6 + backend/Requirements.txt | 5 + backend/app/__init__.py | 22 +- backend/app/models.py | 16 +- backend/app/routes.py | 57 +- frontend/package.json | 9 +- frontend/pnpm-lock.yaml | 1207 +++++++++++++++++++- frontend/src/App.tsx | 26 +- frontend/src/assets/grocery-bg.jpg | Bin 0 -> 22639 bytes frontend/src/components/LoginUtilities.tsx | 135 +++ frontend/src/components/ProtectedRoute.tsx | 8 + frontend/src/main.tsx | 22 +- frontend/src/pages/Home.tsx | 32 +- frontend/src/pages/Login.tsx | 144 +++ frontend/src/pages/Signup.tsx | 156 +++ frontend/src/services/api.ts | 45 +- frontend/src/theme/theme.ts | 18 + requirements.txt | 6 - 19 files changed, 1868 insertions(+), 50 deletions(-) create mode 100644 backend/Requirements.txt create mode 100644 frontend/src/assets/grocery-bg.jpg create mode 100644 frontend/src/components/LoginUtilities.tsx create mode 100644 frontend/src/components/ProtectedRoute.tsx create mode 100644 frontend/src/pages/Login.tsx create mode 100644 frontend/src/pages/Signup.tsx create mode 100644 frontend/src/theme/theme.ts delete mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index 92e6794..ade7172 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,7 @@ __pycache__/ # OS .DS_Store + +# Flask instance folder (local DB, secrets) +backend/instance/ +instance/ diff --git a/README.md b/README.md index 776f3d8..094fb3d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,8 @@ # Flashify e-commerce platform for instant deliveries within 20 mins. + + +To download dependencies and create db locally: + +pip install -r requirements.txt +flask db init / create_all \ No newline at end of file diff --git a/backend/Requirements.txt b/backend/Requirements.txt new file mode 100644 index 0000000..3eaab8f --- /dev/null +++ b/backend/Requirements.txt @@ -0,0 +1,5 @@ +Flask==3.1.2 +Flask-Cors==6.0.2 +Flask-JWT-Extended==4.7.1 +Flask-SQLAlchemy==3.1.1 +python-dotenv==1.2.1 \ No newline at end of file diff --git a/backend/app/__init__.py b/backend/app/__init__.py index 5b8b022..9f8c404 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -1,23 +1,31 @@ from flask import Flask from flask_cors import CORS from flask_sqlalchemy import SQLAlchemy +from flask_jwt_extended import JWTManager db = SQLAlchemy() +jwt = JWTManager() def create_app(): app = Flask(__name__) app.config.from_object("config.Config") - CORS(app) # allow frontend calls - db.init_app(app) # database hookup + # Allow frontend calls + CORS(app) + # Initialize extensions + db.init_app(app) + jwt.init_app(app) + + # Ensure models are imported before create_all + from . import models # noqa: F401 - @app.route("/health") - def health(): - return {"status": "ok"} - + # Create DB tables + with app.app_context(): + db.create_all() + # Register API routes (Blueprint) from .routes import api app.register_blueprint(api) - return app + return app \ No newline at end of file diff --git a/backend/app/models.py b/backend/app/models.py index 38d00e7..35b0d4f 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -1,5 +1,19 @@ +from werkzeug.security import generate_password_hash, check_password_hash from . import db class User(db.Model): + __tablename__ = "users" + id = db.Column(db.Integer, primary_key=True) - email = db.Column(db.String(120), unique=True, nullable=False) + + # Unique + indexed makes login lookups fast and safe + email = db.Column(db.String(255), unique=True, nullable=False, index=True) + + # Store ONLY the hash, never the raw password + password_hash = db.Column(db.String(255), nullable=False) + + def set_password(self, password: str) -> None: + self.password_hash = generate_password_hash(password) + + def check_password(self, password: str) -> bool: + return check_password_hash(self.password_hash, password) diff --git a/backend/app/routes.py b/backend/app/routes.py index f367375..0f69d89 100644 --- a/backend/app/routes.py +++ b/backend/app/routes.py @@ -1,7 +1,58 @@ -from flask import Blueprint, jsonify +from flask import Blueprint, request, jsonify +from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity +from .models import User +from . import db api = Blueprint("api", __name__) -@api.route("/api/health") +@api.get("/api/health") def health(): - return jsonify({"status": "backend running"}) + return jsonify(status="backend running") + + +@api.post("/api/auth/signup") +def signup(): + data = request.get_json(silent=True) or {} + email = (data.get("email") or "").strip().lower() + password = data.get("password") or "" + + if not email or not password: + return jsonify(message="Email and password are required."), 400 + + existing = User.query.filter_by(email=email).first() + if existing: + return jsonify(message="User already exists."), 409 + + user = User(email=email) + user.set_password(password) + + db.session.add(user) + db.session.commit() + + return jsonify(message="Signup successful."), 201 + + +@api.post("/api/auth/login") +def login(): + data = request.get_json(silent=True) or {} + email = (data.get("email") or "").strip().lower() + password = data.get("password") or "" + + if not email or not password: + return jsonify(message="Email and password are required."), 400 + + user = User.query.filter_by(email=email).first() + if not user or not user.check_password(password): + return jsonify(message="Invalid email or password."), 401 + + # JWT identity can be email for now + token = create_access_token(identity=email) + + return jsonify(token=token), 200 + + +@api.get("/api/me") +@jwt_required() +def me(): + email = get_jwt_identity() + return jsonify(email=email), 200 diff --git a/frontend/package.json b/frontend/package.json index c902fa7..1dc669b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,8 +10,15 @@ "preview": "vite preview" }, "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "7.3.7", + "@mui/material": "7.3.7", + "@toolpad/core": "^0.16.0", + "axios": "^1.13.4", "react": "^19.2.0", - "react-dom": "^19.2.0" + "react-dom": "^19.2.0", + "react-router-dom": "^7.13.0" }, "devDependencies": { "@eslint/js": "^9.39.1", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 30536f0..d64ddd5 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -8,12 +8,33 @@ importers: .: dependencies: + '@emotion/react': + specifier: ^11.14.0 + version: 11.14.0(@types/react@19.2.7)(react@19.2.3) + '@emotion/styled': + specifier: ^11.14.1 + version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3) + '@mui/icons-material': + specifier: 7.3.7 + version: 7.3.7(@mui/material@7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react@19.2.3) + '@mui/material': + specifier: 7.3.7 + version: 7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@toolpad/core': + specifier: ^0.16.0 + version: 0.16.0(b10d5de02e195908768946ebbb476668) + axios: + specifier: ^1.13.4 + version: 1.13.4 react: specifier: ^19.2.0 version: 19.2.3 react-dom: specifier: ^19.2.0 version: 19.2.3(react@19.2.3) + react-router-dom: + specifier: ^7.13.0 + version: 7.13.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) devDependencies: '@eslint/js': specifier: ^9.39.1 @@ -29,7 +50,7 @@ importers: version: 19.2.3(@types/react@19.2.7) '@vitejs/plugin-react-swc': specifier: ^4.2.2 - version: 4.2.2(vite@7.3.0(@types/node@24.10.4)) + version: 4.2.2(vite@7.3.0(@types/node@24.10.4)(yaml@2.5.1)) eslint: specifier: ^9.39.1 version: 9.39.2 @@ -50,7 +71,7 @@ importers: version: 8.50.0(eslint@9.39.2)(typescript@5.9.3) vite: specifier: ^7.2.4 - version: 7.3.0(@types/node@24.10.4) + version: 7.3.0(@types/node@24.10.4)(yaml@2.5.1) packages: @@ -109,6 +130,10 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} @@ -121,6 +146,60 @@ packages: resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} + '@emotion/babel-plugin@11.13.5': + resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} + + '@emotion/cache@11.14.0': + resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==} + + '@emotion/hash@0.9.2': + resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + + '@emotion/is-prop-valid@1.4.0': + resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==} + + '@emotion/memoize@0.9.0': + resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} + + '@emotion/react@11.14.0': + resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==} + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/serialize@1.3.3': + resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==} + + '@emotion/sheet@1.4.0': + resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} + + '@emotion/styled@11.14.1': + resolution: {integrity: sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==} + peerDependencies: + '@emotion/react': ^11.0.0-rc.0 + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/unitless@0.10.0': + resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0': + resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==} + peerDependencies: + react: '>=16.8.0' + + '@emotion/utils@1.4.2': + resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==} + + '@emotion/weak-memoize@0.4.0': + resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} + '@esbuild/aix-ppc64@0.27.2': resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} engines: {node: '>=18'} @@ -347,6 +426,166 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@mui/core-downloads-tracker@7.3.7': + resolution: {integrity: sha512-8jWwS6FweMkpyRkrJooamUGe1CQfO1yJ+lM43IyUJbrhHW/ObES+6ry4vfGi8EKaldHL3t3BG1bcLcERuJPcjg==} + + '@mui/icons-material@7.3.7': + resolution: {integrity: sha512-3Q+ulAqG+A1+R4ebgoIs7AccaJhIGy+Xi/9OnvX376jQ6wcy+rz4geDGrxQxCGzdjOQr4Z3NgyFSZCz4T999lA==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@mui/material': ^7.3.7 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/material@7.3.7': + resolution: {integrity: sha512-6bdIxqzeOtBAj2wAsfhWCYyMKPLkRO9u/2o5yexcL0C3APqyy91iGSWgT3H7hg+zR2XgE61+WAu12wXPON8b6A==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@mui/material-pigment-css': ^7.3.7 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@mui/material-pigment-css': + optional: true + '@types/react': + optional: true + + '@mui/private-theming@7.3.7': + resolution: {integrity: sha512-w7r1+CYhG0syCAQUWAuV5zSaU2/67WA9JXUderdb7DzCIJdp/5RmJv6L85wRjgKCMsxFF0Kfn0kPgPbPgw/jdw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/styled-engine@7.3.7': + resolution: {integrity: sha512-y/QkNXv6cF6dZ5APztd/dFWfQ6LHKPx3skyYO38YhQD4+Cxd6sFAL3Z38WMSSC8LQz145Mpp3CcLrSCLKPwYAg==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + + '@mui/system@7.3.7': + resolution: {integrity: sha512-DovL3k+FBRKnhmatzUMyO5bKkhMLlQ9L7Qw5qHrre3m8zCZmE+31NDVBFfqrbrA7sq681qaEIHdkWD5nmiAjyQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + + '@mui/types@7.4.10': + resolution: {integrity: sha512-0+4mSjknSu218GW3isRqoxKRTOrTLd/vHi/7UC4+wZcUrOAqD9kRk7UQRL1mcrzqRoe7s3UT6rsRpbLkW5mHpQ==} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/utils@7.3.7': + resolution: {integrity: sha512-+YjnjMRnyeTkWnspzoxRdiSOgkrcpTikhNPoxOZW0APXx+urHtUoXJ9lbtCZRCA5a4dg5gSbd19alL1DvRs5fg==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/x-data-grid@8.26.0': + resolution: {integrity: sha512-6WDDTYv9ryce7j3S7bPIRMCHvzG8rbU2O/p9/JK+InoZUAL9BaAKwvIjt31KeE0Q5yHiNDd0FWiOUY1vEcoDog==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.9.0 + '@emotion/styled': ^11.8.1 + '@mui/material': ^5.15.14 || ^6.0.0 || ^7.0.0 + '@mui/system': ^5.15.14 || ^6.0.0 || ^7.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + + '@mui/x-date-pickers@8.26.0': + resolution: {integrity: sha512-tW9SrY8jRX0qf/v4ki/x46hsHgJrsFib271QYWOxTEofREWpPrYqdUarRGs21uhHha0spCzJUU1jEAxFKc3LHQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.9.0 + '@emotion/styled': ^11.8.1 + '@mui/material': ^5.15.14 || ^6.0.0 || ^7.0.0 + '@mui/system': ^5.15.14 || ^6.0.0 || ^7.0.0 + date-fns: ^2.25.0 || ^3.2.0 || ^4.0.0 + date-fns-jalali: ^2.13.0-0 || ^3.2.0-0 || ^4.0.0-0 + dayjs: ^1.10.7 + luxon: ^3.0.2 + moment: ^2.29.4 + moment-hijri: ^2.1.2 || ^3.0.0 + moment-jalaali: ^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + date-fns: + optional: true + date-fns-jalali: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + moment-hijri: + optional: true + moment-jalaali: + optional: true + + '@mui/x-internals@8.26.0': + resolution: {integrity: sha512-B9OZau5IQUvIxwpJZhoFJKqRpmWf5r0yMmSXjQuqb5WuqM755EuzWJOenY48denGoENzMLT8hQpA0hRTeU2IPA==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@mui/x-virtualizer@0.3.3': + resolution: {integrity: sha512-6ugUh7UAhQYdgPgHLu181zqufh3Y8IqEU9Pe6Huzj0xkRi3NwMx/ZzvrHf2WazNOh2uLhQ5ZM2wFqDu3mxBWZA==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@rolldown/pluginutils@1.0.0-beta.47': resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==} @@ -535,6 +774,32 @@ packages: '@swc/types@0.1.25': resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} + '@toolpad/core@0.16.0': + resolution: {integrity: sha512-/Iiyubp1u8L4DZqIgP1N0mhOxXpk9+I05xg+Hbk7VYUVEMlVmF+x7+FzPxocOWrvgAdeNZfyCZhY9u99SEQbtQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/cache': ^11 + '@emotion/react': ^11 + '@mui/icons-material': ^7.0.0-beta || ^7.0.0 + '@mui/material': ^7.0.0-beta || ^7.0.0 + '@tanstack/react-router': ^1 + next: ^14 || ^15 + react: ^18 || ^19 + react-dom: ^18 || ^19 + react-router: ^7 + peerDependenciesMeta: + '@tanstack/react-router': + optional: true + next: + optional: true + react-router: + optional: true + + '@toolpad/utils@0.16.0': + resolution: {integrity: sha512-nqM7lk32OJwPFwEJyUoZ1Wvm1qwTlJ+ZP8w+sF9U5rW+ufcEm09sav4h+gwdjx6199B765omqu3AS41nvkWBBw==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -544,11 +809,22 @@ packages: '@types/node@24.10.4': resolution: {integrity: sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==} + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: '@types/react': ^19.2.0 + '@types/react-transition-group@4.4.12': + resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==} + peerDependencies: + '@types/react': '*' + '@types/react@19.2.7': resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} @@ -634,9 +910,22 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.13.4: + resolution: {integrity: sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==} + + babel-plugin-macros@3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -655,6 +944,10 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} @@ -666,6 +959,21 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + clipboardy@4.0.0: + resolution: {integrity: sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w==} + engines: {node: '>=18'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -673,12 +981,27 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -686,6 +1009,9 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + dayjs@1.11.13: + resolution: {integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -698,9 +1024,39 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + electron-to-chromium@1.5.267: resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + esbuild@0.27.2: resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} engines: {node: '>=18'} @@ -767,9 +1123,16 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + execa@8.0.1: + resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} + engines: {node: '>=16.17'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-json-patch@3.1.1: + resolution: {integrity: sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==} + fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -789,6 +1152,9 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -800,15 +1166,43 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@8.0.1: + resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} + engines: {node: '>=16'} + glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} @@ -821,16 +1215,39 @@ packages: resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} engines: {node: '>=18'} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + hermes-estree@0.25.1: resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + + human-signals@5.0.0: + resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} + engines: {node: '>=16.17.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -847,6 +1264,21 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -855,6 +1287,23 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-stream@3.0.0: + resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + + is64bit@2.0.0: + resolution: {integrity: sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw==} + engines: {node: '>=18'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -873,6 +1322,9 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -891,6 +1343,9 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -898,9 +1353,32 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mimic-fn@4.0.0: + resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} + engines: {node: '>=12'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -922,6 +1400,22 @@ packages: node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + npm-run-path@5.3.0: + resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + onetime@6.0.0: + resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} + engines: {node: '>=12'} + + oppa@0.4.0: + resolution: {integrity: sha512-DFvM3+F+rB/igo3FRnkDWitjZgBH9qZAn68IacYHsqbZBKwuTA+LdD4zSJiQtgQpWq7M08we5FlGAVHz0yW7PQ==} + engines: {node: '>=10'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -938,6 +1432,10 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -946,6 +1444,20 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -961,6 +1473,17 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + prettier@3.5.3: + resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==} + engines: {node: '>=14'} + hasBin: true + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -970,14 +1493,51 @@ packages: peerDependencies: react: ^19.2.3 + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@19.2.4: + resolution: {integrity: sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==} + + react-router-dom@7.13.0: + resolution: {integrity: sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.13.0: + resolution: {integrity: sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react-transition-group@4.4.5: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + react@19.2.3: resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} engines: {node: '>=0.10.0'} + reselect@5.1.1: + resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + rollup@4.53.5: resolution: {integrity: sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -995,6 +1555,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1003,22 +1566,49 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + strip-final-newline@3.0.0: + resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} + engines: {node: '>=12'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + stylis@4.2.0: + resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + system-architecture@0.1.0: + resolution: {integrity: sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + title@4.0.1: + resolution: {integrity: sha512-xRnPkJx9nvE5MF6LkB5e8QJjE2FW8269wTu/LQdf7zZqBgPly0QJPf/CWAo7srj5so4yXfoLEdCFgurlpi47zg==} + hasBin: true + ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -1053,6 +1643,11 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + vite@7.3.0: resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1105,6 +1700,20 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml-diff-patch@2.0.0: + resolution: {integrity: sha512-RhfIQPGcKSZhsUmsczXAeg5jNhWXk3tAmhl2kjfZthdyaL0XXXOpvRozUp22HvPStmZsHu8T30/UEfX9oIwGxw==} + engines: {node: '>=14'} + hasBin: true + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + yaml@2.5.1: + resolution: {integrity: sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==} + engines: {node: '>= 14'} + hasBin: true + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -1197,6 +1806,8 @@ snapshots: dependencies: '@babel/types': 7.28.5 + '@babel/runtime@7.28.6': {} + '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 @@ -1220,6 +1831,89 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@emotion/babel-plugin@11.13.5': + dependencies: + '@babel/helper-module-imports': 7.27.1 + '@babel/runtime': 7.28.6 + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/serialize': 1.3.3 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.2.0 + transitivePeerDependencies: + - supports-color + + '@emotion/cache@11.14.0': + dependencies: + '@emotion/memoize': 0.9.0 + '@emotion/sheet': 1.4.0 + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + stylis: 4.2.0 + + '@emotion/hash@0.9.2': {} + + '@emotion/is-prop-valid@1.4.0': + dependencies: + '@emotion/memoize': 0.9.0 + + '@emotion/memoize@0.9.0': {} + + '@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3)': + dependencies: + '@babel/runtime': 7.28.6 + '@emotion/babel-plugin': 11.13.5 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.3) + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + hoist-non-react-statics: 3.3.2 + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.7 + transitivePeerDependencies: + - supports-color + + '@emotion/serialize@1.3.3': + dependencies: + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/unitless': 0.10.0 + '@emotion/utils': 1.4.2 + csstype: 3.2.3 + + '@emotion/sheet@1.4.0': {} + + '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3)': + dependencies: + '@babel/runtime': 7.28.6 + '@emotion/babel-plugin': 11.13.5 + '@emotion/is-prop-valid': 1.4.0 + '@emotion/react': 11.14.0(@types/react@19.2.7)(react@19.2.3) + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.3) + '@emotion/utils': 1.4.2 + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.7 + transitivePeerDependencies: + - supports-color + + '@emotion/unitless@0.10.0': {} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.2.3)': + dependencies: + react: 19.2.3 + + '@emotion/utils@1.4.2': {} + + '@emotion/weak-memoize@0.4.0': {} + '@esbuild/aix-ppc64@0.27.2': optional: true @@ -1374,6 +2068,154 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@mui/core-downloads-tracker@7.3.7': {} + + '@mui/icons-material@7.3.7(@mui/material@7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react@19.2.3)': + dependencies: + '@babel/runtime': 7.28.6 + '@mui/material': 7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.7 + + '@mui/material@7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@babel/runtime': 7.28.6 + '@mui/core-downloads-tracker': 7.3.7 + '@mui/system': 7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3) + '@mui/types': 7.4.10(@types/react@19.2.7) + '@mui/utils': 7.3.7(@types/react@19.2.7)(react@19.2.3) + '@popperjs/core': 2.11.8 + '@types/react-transition-group': 4.4.12(@types/react@19.2.7) + clsx: 2.1.1 + csstype: 3.2.3 + prop-types: 15.8.1 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-is: 19.2.4 + react-transition-group: 4.4.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.2.7)(react@19.2.3) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3) + '@types/react': 19.2.7 + + '@mui/private-theming@7.3.7(@types/react@19.2.7)(react@19.2.3)': + dependencies: + '@babel/runtime': 7.28.6 + '@mui/utils': 7.3.7(@types/react@19.2.7)(react@19.2.3) + prop-types: 15.8.1 + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.7 + + '@mui/styled-engine@7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(react@19.2.3)': + dependencies: + '@babel/runtime': 7.28.6 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/sheet': 1.4.0 + csstype: 3.2.3 + prop-types: 15.8.1 + react: 19.2.3 + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.2.7)(react@19.2.3) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3) + + '@mui/system@7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3)': + dependencies: + '@babel/runtime': 7.28.6 + '@mui/private-theming': 7.3.7(@types/react@19.2.7)(react@19.2.3) + '@mui/styled-engine': 7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(react@19.2.3) + '@mui/types': 7.4.10(@types/react@19.2.7) + '@mui/utils': 7.3.7(@types/react@19.2.7)(react@19.2.3) + clsx: 2.1.1 + csstype: 3.2.3 + prop-types: 15.8.1 + react: 19.2.3 + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.2.7)(react@19.2.3) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3) + '@types/react': 19.2.7 + + '@mui/types@7.4.10(@types/react@19.2.7)': + dependencies: + '@babel/runtime': 7.28.6 + optionalDependencies: + '@types/react': 19.2.7 + + '@mui/utils@7.3.7(@types/react@19.2.7)(react@19.2.3)': + dependencies: + '@babel/runtime': 7.28.6 + '@mui/types': 7.4.10(@types/react@19.2.7) + '@types/prop-types': 15.7.15 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 19.2.3 + react-is: 19.2.4 + optionalDependencies: + '@types/react': 19.2.7 + + '@mui/x-data-grid@8.26.0(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@mui/material@7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mui/system@7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@babel/runtime': 7.28.6 + '@mui/material': 7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@mui/system': 7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3) + '@mui/utils': 7.3.7(@types/react@19.2.7)(react@19.2.3) + '@mui/x-internals': 8.26.0(@types/react@19.2.7)(react@19.2.3) + '@mui/x-virtualizer': 0.3.3(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + clsx: 2.1.1 + prop-types: 15.8.1 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + use-sync-external-store: 1.6.0(react@19.2.3) + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.2.7)(react@19.2.3) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3) + transitivePeerDependencies: + - '@types/react' + + '@mui/x-date-pickers@8.26.0(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@mui/material@7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mui/system@7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(dayjs@1.11.13)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@babel/runtime': 7.28.6 + '@mui/material': 7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@mui/system': 7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3) + '@mui/utils': 7.3.7(@types/react@19.2.7)(react@19.2.3) + '@mui/x-internals': 8.26.0(@types/react@19.2.7)(react@19.2.3) + '@types/react-transition-group': 4.4.12(@types/react@19.2.7) + clsx: 2.1.1 + prop-types: 15.8.1 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-transition-group: 4.4.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.2.7)(react@19.2.3) + '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3) + dayjs: 1.11.13 + transitivePeerDependencies: + - '@types/react' + + '@mui/x-internals@8.26.0(@types/react@19.2.7)(react@19.2.3)': + dependencies: + '@babel/runtime': 7.28.6 + '@mui/utils': 7.3.7(@types/react@19.2.7)(react@19.2.3) + react: 19.2.3 + reselect: 5.1.1 + use-sync-external-store: 1.6.0(react@19.2.3) + transitivePeerDependencies: + - '@types/react' + + '@mui/x-virtualizer@0.3.3(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@babel/runtime': 7.28.6 + '@mui/utils': 7.3.7(@types/react@19.2.7)(react@19.2.3) + '@mui/x-internals': 8.26.0(@types/react@19.2.7)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + transitivePeerDependencies: + - '@types/react' + + '@popperjs/core@2.11.8': {} + '@rolldown/pluginutils@1.0.0-beta.47': {} '@rollup/rollup-android-arm-eabi@4.53.5': @@ -1494,6 +2336,47 @@ snapshots: dependencies: '@swc/counter': 0.1.3 + '@toolpad/core@0.16.0(b10d5de02e195908768946ebbb476668)': + dependencies: + '@babel/runtime': 7.28.6 + '@emotion/cache': 11.14.0 + '@emotion/react': 11.14.0(@types/react@19.2.7)(react@19.2.3) + '@mui/icons-material': 7.3.7(@mui/material@7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@types/react@19.2.7)(react@19.2.3) + '@mui/material': 7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@mui/utils': 7.3.7(@types/react@19.2.7)(react@19.2.3) + '@mui/x-data-grid': 8.26.0(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@mui/material@7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mui/system@7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@mui/x-date-pickers': 8.26.0(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@mui/material@7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@mui/system@7.3.7(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(react@19.2.3))(@types/react@19.2.7)(dayjs@1.11.13)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@toolpad/utils': 0.16.0(react@19.2.3) + client-only: 0.0.1 + dayjs: 1.11.13 + invariant: 2.2.4 + path-to-regexp: 6.3.0 + prop-types: 15.8.1 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + react-router: 7.13.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + transitivePeerDependencies: + - '@emotion/styled' + - '@mui/system' + - '@types/react' + - date-fns + - date-fns-jalali + - luxon + - moment + - moment-hijri + - moment-jalaali + + '@toolpad/utils@0.16.0(react@19.2.3)': + dependencies: + invariant: 2.2.4 + prettier: 3.5.3 + react: 19.2.3 + react-is: 19.2.4 + title: 4.0.1 + yaml: 2.5.1 + yaml-diff-patch: 2.0.0 + '@types/estree@1.0.8': {} '@types/json-schema@7.0.15': {} @@ -1502,10 +2385,18 @@ snapshots: dependencies: undici-types: 7.16.0 + '@types/parse-json@4.0.2': {} + + '@types/prop-types@15.7.15': {} + '@types/react-dom@19.2.3(@types/react@19.2.7)': dependencies: '@types/react': 19.2.7 + '@types/react-transition-group@4.4.12(@types/react@19.2.7)': + dependencies: + '@types/react': 19.2.7 + '@types/react@19.2.7': dependencies: csstype: 3.2.3 @@ -1601,11 +2492,11 @@ snapshots: '@typescript-eslint/types': 8.50.0 eslint-visitor-keys: 4.2.1 - '@vitejs/plugin-react-swc@4.2.2(vite@7.3.0(@types/node@24.10.4))': + '@vitejs/plugin-react-swc@4.2.2(vite@7.3.0(@types/node@24.10.4)(yaml@2.5.1))': dependencies: '@rolldown/pluginutils': 1.0.0-beta.47 '@swc/core': 1.15.5 - vite: 7.3.0(@types/node@24.10.4) + vite: 7.3.0(@types/node@24.10.4)(yaml@2.5.1) transitivePeerDependencies: - '@swc/helpers' @@ -1626,8 +2517,26 @@ snapshots: dependencies: color-convert: 2.0.1 + arg@5.0.2: {} + argparse@2.0.1: {} + asynckit@0.4.0: {} + + axios@1.13.4: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + babel-plugin-macros@3.1.0: + dependencies: + '@babel/runtime': 7.28.6 + cosmiconfig: 7.1.0 + resolve: 1.22.11 + balanced-match@1.0.2: {} baseline-browser-mapping@2.9.8: {} @@ -1649,6 +2558,11 @@ snapshots: node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + callsites@3.1.0: {} caniuse-lite@1.0.30001760: {} @@ -1658,16 +2572,44 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chalk@5.6.2: {} + + client-only@0.0.1: {} + + clipboardy@4.0.0: + dependencies: + execa: 8.0.1 + is-wsl: 3.1.0 + is64bit: 2.0.0 + + clsx@2.1.1: {} + color-convert@2.0.1: dependencies: color-name: 1.1.4 color-name@1.1.4: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + concat-map@0.0.1: {} + convert-source-map@1.9.0: {} + convert-source-map@2.0.0: {} + cookie@1.1.1: {} + + cosmiconfig@7.1.0: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.1 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -1676,14 +2618,48 @@ snapshots: csstype@3.2.3: {} + dayjs@1.11.13: {} + debug@4.4.3: dependencies: ms: 2.1.3 deep-is@0.1.4: {} + delayed-stream@1.0.0: {} + + dom-helpers@5.2.1: + dependencies: + '@babel/runtime': 7.28.6 + csstype: 3.2.3 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + electron-to-chromium@1.5.267: {} + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + esbuild@0.27.2: optionalDependencies: '@esbuild/aix-ppc64': 0.27.2 @@ -1798,8 +2774,22 @@ snapshots: esutils@2.0.3: {} + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + fast-deep-equal@3.1.3: {} + fast-json-patch@3.1.1: {} + fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} @@ -1812,6 +2802,8 @@ snapshots: dependencies: flat-cache: 4.0.1 + find-root@1.1.0: {} + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -1824,11 +2816,43 @@ snapshots: flatted@3.3.3: {} + follow-redirects@1.15.11: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + gensync@1.0.0-beta.2: {} + get-intrinsic@1.3.0: + 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 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@8.0.1: {} + glob-parent@6.0.2: dependencies: is-glob: 4.0.3 @@ -1837,14 +2861,32 @@ snapshots: globals@16.5.0: {} + gopd@1.2.0: {} + has-flag@4.0.0: {} + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + hermes-estree@0.25.1: {} hermes-parser@0.25.1: dependencies: hermes-estree: 0.25.1 + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + + human-signals@5.0.0: {} + ignore@5.3.2: {} ignore@7.0.5: {} @@ -1856,12 +2898,38 @@ snapshots: imurmurhash@0.1.4: {} + invariant@2.2.4: + dependencies: + loose-envify: 1.4.0 + + is-arrayish@0.2.1: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-docker@3.0.0: {} + is-extglob@2.1.1: {} is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-stream@3.0.0: {} + + is-wsl@3.1.0: + dependencies: + is-inside-container: 1.0.0 + + is64bit@2.0.0: + dependencies: + system-architecture: 0.1.0 + isexe@2.0.0: {} js-tokens@4.0.0: {} @@ -1874,6 +2942,8 @@ snapshots: json-buffer@3.0.1: {} + json-parse-even-better-errors@2.3.1: {} + json-schema-traverse@0.4.1: {} json-stable-stringify-without-jsonify@1.0.1: {} @@ -1889,16 +2959,34 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lines-and-columns@1.2.4: {} + locate-path@6.0.0: dependencies: p-locate: 5.0.0 lodash.merge@4.6.2: {} + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + lru-cache@5.1.1: dependencies: yallist: 3.1.1 + math-intrinsics@1.1.0: {} + + merge-stream@2.0.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mimic-fn@4.0.0: {} + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 @@ -1915,6 +3003,20 @@ snapshots: node-releases@2.0.27: {} + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + object-assign@4.1.1: {} + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + oppa@0.4.0: + dependencies: + chalk: 4.1.2 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -1936,10 +3038,25 @@ snapshots: dependencies: callsites: 3.1.0 + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.27.1 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + path-exists@4.0.0: {} path-key@3.1.1: {} + path-key@4.0.0: {} + + path-parse@1.0.7: {} + + path-to-regexp@6.3.0: {} + + path-type@4.0.0: {} + picocolors@1.1.1: {} picomatch@4.0.3: {} @@ -1952,6 +3069,16 @@ snapshots: prelude-ls@1.2.1: {} + prettier@3.5.3: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + proxy-from-env@1.1.0: {} + punycode@2.3.1: {} react-dom@19.2.3(react@19.2.3): @@ -1959,10 +3086,45 @@ snapshots: react: 19.2.3 scheduler: 0.27.0 + react-is@16.13.1: {} + + react-is@19.2.4: {} + + react-router-dom@7.13.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-router: 7.13.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + + react-router@7.13.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + cookie: 1.1.1 + react: 19.2.3 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 19.2.3(react@19.2.3) + + react-transition-group@4.4.5(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + '@babel/runtime': 7.28.6 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react@19.2.3: {} + reselect@5.1.1: {} + resolve-from@4.0.0: {} + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + rollup@4.53.5: dependencies: '@types/estree': 1.0.8 @@ -1997,25 +3159,45 @@ snapshots: semver@7.7.3: {} + set-cookie-parser@2.7.2: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + signal-exit@4.1.0: {} + source-map-js@1.2.1: {} + source-map@0.5.7: {} + + strip-final-newline@3.0.0: {} + strip-json-comments@3.1.1: {} + stylis@4.2.0: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} + + system-architecture@0.1.0: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + title@4.0.1: + dependencies: + arg: 5.0.2 + chalk: 5.6.2 + clipboardy: 4.0.0 + ts-api-utils@2.1.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -2049,7 +3231,11 @@ snapshots: dependencies: punycode: 2.3.1 - vite@7.3.0(@types/node@24.10.4): + use-sync-external-store@1.6.0(react@19.2.3): + dependencies: + react: 19.2.3 + + vite@7.3.0(@types/node@24.10.4)(yaml@2.5.1): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -2060,6 +3246,7 @@ snapshots: optionalDependencies: '@types/node': 24.10.4 fsevents: 2.3.3 + yaml: 2.5.1 which@2.0.2: dependencies: @@ -2069,6 +3256,16 @@ snapshots: yallist@3.1.1: {} + yaml-diff-patch@2.0.0: + dependencies: + fast-json-patch: 3.1.1 + oppa: 0.4.0 + yaml: 2.5.1 + + yaml@1.10.2: {} + + yaml@2.5.1: {} + yocto-queue@0.1.0: {} zod-validation-error@4.0.2(zod@4.2.1): diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8172e6d..3f13d3a 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,8 +1,26 @@ -// src/App.tsx -import Home from "./pages/Home"; +import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import Home from './pages/Home'; +import Login from './pages/Login'; +import Signup from './pages/Signup'; +import ProtectedRoute from './components/ProtectedRoute'; function App() { - return ; + return ( + + + + + + } + /> + } /> + } /> + + + ); } -export default App; \ No newline at end of file +export default App; diff --git a/frontend/src/assets/grocery-bg.jpg b/frontend/src/assets/grocery-bg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9b42b449375886264e6c69f6bc8acea711a42803 GIT binary patch literal 22639 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 +} + +export function Subtitle() { + return ( + + Welcome back! Please log in to continue. + + ); +} + +export function CustomEmailField() { + return ( + + + + ), + }, + }} + /> + ); +} + +export function CustomPasswordField() { + const [show, setShow] = React.useState(false); + + return ( + + Password + + setShow(!show)}> + {show ? : } + + + } + label="Password" + /> + + ); +} + +export function CustomButton() { + return ( + + ); +} + +export function RememberMeCheckbox() { + const theme = useTheme(); + + return ( + } + slotProps={{ + typography: { + fontSize: theme.typography.pxToRem(14), + color: 'text.secondary', + }, + }} + /> + ); +} + +export function SignUpLink() { + return ( + + Sign up + + ); +} + +export function LoginLink() { + return ( + + Already have an account? Log in + + ); +} + +export function ForgotPasswordLink() { + return ( + + Forgot password? + + ); +} diff --git a/frontend/src/components/ProtectedRoute.tsx b/frontend/src/components/ProtectedRoute.tsx new file mode 100644 index 0000000..8e1aa00 --- /dev/null +++ b/frontend/src/components/ProtectedRoute.tsx @@ -0,0 +1,8 @@ +import React from 'react'; +import { Navigate } from 'react-router-dom'; + +export default function ProtectedRoute({ children }: { children: JSX.Element }) { + const token = localStorage.getItem('token'); + if (!token) return ; + return children; +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index bef5202..9a78ddd 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,10 +1,14 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import './index.css' -import App from './App.tsx' +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { ThemeProvider, CssBaseline } from '@mui/material'; +import App from './App'; +import theme from './theme/theme'; -createRoot(document.getElementById('root')!).render( - - - , -) +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + + , +); diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index c7fcdc4..73e522c 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -1,21 +1,37 @@ -import { useEffect, useState } from "react"; -import { getHealth } from "../services/api"; +import { useEffect, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Box, Button, Typography } from '@mui/material'; +import { getHealth } from '../services/api'; function Home() { - const [status, setStatus] = useState("loading..."); + const [status, setStatus] = useState('loading...'); + const navigate = useNavigate(); useEffect(() => { getHealth() .then((data) => setStatus(data.status)) - .catch(() => setStatus("backend not reachable")); + .catch(() => setStatus('backend not reachable')); }, []); + const handleLogout = () => { + localStorage.removeItem('token'); + navigate('/login', { replace: true }); + }; + return ( -
-

Frontend ↔ Backend Test

-

Backend status: {status}

-
+ + + Frontend ↔ Backend Test + + + + + Backend status: {status} + ); } export default Home; + diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx new file mode 100644 index 0000000..a942753 --- /dev/null +++ b/frontend/src/pages/Login.tsx @@ -0,0 +1,144 @@ +import * as React from 'react'; +import { Box, Alert } from '@mui/material'; +import { SignInPage, type AuthProvider, type AuthResponse } from '@toolpad/core/SignInPage'; +import groceryBg from '../assets/grocery-bg.jpg'; +import { useNavigate } from 'react-router-dom'; + +import { + Title, + CustomEmailField, + CustomPasswordField, + CustomButton, + RememberMeCheckbox, + SignUpLink, + ForgotPasswordLink, + providers, +} from '../components/LoginUtilities'; + +import {api} from '../services/api'; + + +export default function Login() { + const [error, setError] = React.useState(null); + const navigate = useNavigate(); + + const SubtitleWithErrors = React.useCallback(() => { + if (error) { + return ( + + {error} + + ); + } + return ( + + Welcome back! Please log in to continue. + + ); + }, [error]); + + const handleSignIn = React.useCallback( + async ( + _provider: AuthProvider, + formData?: any, + _callbackUrl?: string, + ): Promise => { + setError(null); + + // Toolpad types formData as `any`, but it is FormData at runtime. + const fd: FormData | null = formData instanceof FormData ? formData : null; + + const email = (fd?.get('email') as string | null) ?? null; + const password = (fd?.get('password') as string | null) ?? null; + + if (!email || !password) { + const msg = 'Please enter both email and password.'; + setError(msg); + return {}; + } + + try { + // ✅ CALL BACKEND + // const res = await api.post('/auth/login', { email, password }); + // return res.data; // { token } + + const res = await api.post('/auth/login', { email, password }); + console.log('Login successful:', res); + + + // ✅ STORE JWT + localStorage.setItem('token', res?.data?.token); + + // Optional: go somewhere after login + // window.location.href = '/'; + navigate('/', { replace: true }) + + return {}; + } catch (err: any) { + // ✅ Show backend message if present + const msg = + err?.response?.data?.message ?? + 'Sign-in failed. Please check your credentials and try again.'; + setError(msg); + return {}; + } + }, + [], + ); + + return ( + + {/* Blurred background */} + + + {/* Dark overlay */} + + + {/* Login card */} + + + + + ); +} diff --git a/frontend/src/pages/Signup.tsx b/frontend/src/pages/Signup.tsx new file mode 100644 index 0000000..e4e089f --- /dev/null +++ b/frontend/src/pages/Signup.tsx @@ -0,0 +1,156 @@ +import * as React from 'react'; +import { Box, Alert, Button } from '@mui/material'; +import { SignInPage, type AuthProvider, type AuthResponse } from '@toolpad/core/SignInPage'; +import { useNavigate } from 'react-router-dom'; +import groceryBg from '../assets/grocery-bg.jpg'; + +import { + CustomEmailField, + CustomPasswordField, + RememberMeCheckbox, + LoginLink, + providers, +} from '../components/LoginUtilities'; + +import { signup, login } from '../services/api'; + +function Title() { + return

Sign up

; +} + +function Subtitle({ error }: { error: string | null }) { + if (error) { + return ( + + {error} + + ); + } + + return ( + + Create an account to continue. + + ); +} + +function SubmitButton() { + return ( + + ); +} + +export default function Signup() { + const navigate = useNavigate(); + const [error, setError] = React.useState(null); + + const SubtitleWithErrors = React.useCallback(() => { + return ; + }, [error]); + + const handleSignUp = React.useCallback( + async ( + _provider: AuthProvider, + formData?: any, + _callbackUrl?: string, + ): Promise => { + setError(null); + + const fd: FormData | null = formData instanceof FormData ? formData : null; + const email = (fd?.get('email') as string | null) ?? null; + const password = (fd?.get('password') as string | null) ?? null; + + if (!email || !password) { + const msg = 'Please enter both email and password.'; + setError(msg); + return {}; + } + + try { + // 1) create the account + await signup(email, password); + + // 2) auto-login immediately (consistent UX) + const res = await login(email, password); + localStorage.setItem('token', res.token); + + // 3) go to Home + navigate('/', { replace: true }); + + return {}; + } catch (err: any) { + const msg = + err?.response?.data?.message ?? + 'Signup failed. Please try again.'; + setError(msg); + return {}; + } + }, + [navigate], + ); + + return ( + + {/* Blurred background */} + + + {/* Dark overlay */} + + + {/* Signup card */} + + + + + ); +} diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index feb3efa..1a45ccf 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -1,11 +1,44 @@ -const API_BASE_URL = "http://127.0.0.1:5000"; +import axios from 'axios'; -export async function getHealth() { - const response = await fetch(`${API_BASE_URL}/api/health`); +const API_BASE_URL = 'http://127.0.0.1:5000/api'; + +export const api = axios.create({ + baseURL: API_BASE_URL, + headers: { + 'Content-Type': 'application/json', + }, +}); - if (!response.ok) { - throw new Error("Backend not reachable"); +//Automatically attach JWT to every request (if present) +api.interceptors.request.use((config) => { + const token = localStorage.getItem('token'); + if (token) { + config.headers = config.headers ?? {}; + config.headers.Authorization = `Bearer ${token}`; } + return config; +}); + +//Health check (public) +export async function getHealth() { + const res = await api.get('/health'); + return res.data; +} + +//Auth APIs + +export async function login(email: string, password: string) { + const res = await api.post('/auth/login', { email, password }); + return res.data; // { token } +} + +export async function signup(email: string, password: string) { + const res = await api.post('/auth/signup', { email, password }); + return res.data; +} - return response.json(); +// Protected user info +export async function me() { + const res = await api.get('/me'); + return res.data; } diff --git a/frontend/src/theme/theme.ts b/frontend/src/theme/theme.ts new file mode 100644 index 0000000..3d73b5d --- /dev/null +++ b/frontend/src/theme/theme.ts @@ -0,0 +1,18 @@ +import { createTheme } from '@mui/material/styles'; + +const theme = createTheme({ + palette: { + mode: 'light', + primary: { + main: '#2e7d32', + }, + success: { + main: '#2e7d32', + }, + }, + shape: { + borderRadius: 10, + }, +}); + +export default theme; diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 259b318..0000000 --- a/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ -Flask==3.0.0 -Flask-Cors==4.0.0 -Werkzeug==3.0.1 -click==8.1.7 -itsdangerous==2.1.2 -Jinja2==3.1.3 \ No newline at end of file From 0ebda90a48c7d99c6b0190cbef6f51bb7b4b6007 Mon Sep 17 00:00:00 2001 From: Meha Dave Date: Thu, 12 Feb 2026 15:20:26 -0800 Subject: [PATCH 2/2] Removed health api and added addresses and location pages --- backend/app/__init__.py | 22 ++- backend/app/models.py | 46 ++++- backend/app/routes/address_routes.py | 99 ++++++++++ .../app/{routes.py => routes/auth_routes.py} | 12 +- backend/app/routes/location_routes.py | 37 ++++ frontend/src/App.tsx | 31 +++- frontend/src/main.tsx | 15 +- frontend/src/pages/ChooseLocation.tsx | 96 ++++++++++ frontend/src/pages/Home.tsx | 6 - frontend/src/pages/ManageAddresses.tsx | 170 ++++++++++++++++++ frontend/src/services/api.ts | 58 +++++- frontend/src/theme/theme.ts | 79 +++++++- 12 files changed, 627 insertions(+), 44 deletions(-) create mode 100644 backend/app/routes/address_routes.py rename backend/app/{routes.py => routes/auth_routes.py} (90%) create mode 100644 backend/app/routes/location_routes.py create mode 100644 frontend/src/pages/ChooseLocation.tsx create mode 100644 frontend/src/pages/ManageAddresses.tsx diff --git a/backend/app/__init__.py b/backend/app/__init__.py index 9f8c404..10cf24b 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -10,13 +10,13 @@ def create_app(): app = Flask(__name__) app.config.from_object("config.Config") - # Allow frontend calls - CORS(app) - - # Initialize extensions + # Initialize extensions FIRST db.init_app(app) jwt.init_app(app) + # Apply CORS BEFORE registering blueprints + CORS(app) + # Ensure models are imported before create_all from . import models # noqa: F401 @@ -25,7 +25,19 @@ def create_app(): db.create_all() # Register API routes (Blueprint) - from .routes import api + from .routes.auth_routes import api + from .routes.location_routes import location_bp + from .routes.address_routes import address_bp + app.register_blueprint(api) + app.register_blueprint(location_bp) + app.register_blueprint(address_bp) + + #Debug: Print registered routes + print("\n=== REGISTERED ROUTES ===") + for r in app.url_map.iter_rules(): + print(r) + print("========================\n") + return app \ No newline at end of file diff --git a/backend/app/models.py b/backend/app/models.py index 35b0d4f..8f38c83 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -1,19 +1,59 @@ +from datetime import datetime from werkzeug.security import generate_password_hash, check_password_hash from . import db + class User(db.Model): __tablename__ = "users" id = db.Column(db.Integer, primary_key=True) - # Unique + indexed makes login lookups fast and safe email = db.Column(db.String(255), unique=True, nullable=False, index=True) - - # Store ONLY the hash, never the raw password password_hash = db.Column(db.String(255), nullable=False) + # Location selection + location_label = db.Column(db.String(255), nullable=True) + location_lat = db.Column(db.Float, nullable=True) + location_lng = db.Column(db.Float, nullable=True) + + # relationship (optional but recommended) + addresses = db.relationship( + "Address", + backref="user", + lazy=True, + cascade="all, delete-orphan", + ) + def set_password(self, password: str) -> None: self.password_hash = generate_password_hash(password) def check_password(self, password: str) -> bool: return check_password_hash(self.password_hash, password) + + +# NEW TABLE +class Address(db.Model): + __tablename__ = "addresses" + + id = db.Column(db.Integer, primary_key=True) + + user_id = db.Column( + db.Integer, + db.ForeignKey("users.id"), + nullable=False, + index=True, + ) + + label = db.Column(db.String(50), nullable=False) # Home / Work / Other + line1 = db.Column(db.String(255), nullable=False) + line2 = db.Column(db.String(255), nullable=True) + city = db.Column(db.String(100), nullable=False) + state = db.Column(db.String(100), nullable=False) + zip = db.Column(db.String(20), nullable=False) + + lat = db.Column(db.Float, nullable=True) + lng = db.Column(db.Float, nullable=True) + + is_default = db.Column(db.Boolean, default=False, nullable=False) + + created_at = db.Column(db.DateTime, default=datetime.utcnow) diff --git a/backend/app/routes/address_routes.py b/backend/app/routes/address_routes.py new file mode 100644 index 0000000..ea6b2c1 --- /dev/null +++ b/backend/app/routes/address_routes.py @@ -0,0 +1,99 @@ +from flask import Blueprint, request, jsonify +from flask_jwt_extended import jwt_required, get_jwt_identity + +from .. import db +from ..models import Address + +address_bp = Blueprint("addresses", __name__, url_prefix="/api/addresses") + +@address_bp.before_request +def handle_preflight(): + if request.method == "OPTIONS": + return "", 204 + +@address_bp.route("", methods=["GET"]) +@jwt_required() +def list_addresses(): + user_id = get_jwt_identity() + addresses = ( + Address.query.filter_by(user_id=user_id) + .order_by(Address.id.desc()) + .all() + ) + return jsonify([serialize_address(a) for a in addresses]), 200 + + +@address_bp.route("", methods=["POST"]) +@jwt_required() +def create_address(): + user_id = get_jwt_identity() + data = request.get_json() or {} + + required = ["label", "line1", "city", "state", "zip"] + missing = [k for k in required if not data.get(k)] + if missing: + return jsonify({"message": f"Missing fields: {', '.join(missing)}"}), 400 + + is_default = bool(data.get("is_default", False)) + if is_default: + Address.query.filter_by(user_id=user_id, is_default=True).update({"is_default": False}) + + addr = Address( + user_id=user_id, + label=data["label"], + line1=data["line1"], + line2=data.get("line2"), + city=data["city"], + state=data["state"], + zip=data["zip"], + lat=data.get("lat"), + lng=data.get("lng"), + is_default=is_default, + ) + db.session.add(addr) + db.session.commit() + + return jsonify(serialize_address(addr)), 201 + + +@address_bp.route("//default", methods=["PATCH"]) +@jwt_required() +def set_default_address(address_id): + user_id = get_jwt_identity() + addr = Address.query.filter_by(id=address_id, user_id=user_id).first() + if not addr: + return jsonify({"message": "Address not found"}), 404 + + Address.query.filter_by(user_id=user_id, is_default=True).update({"is_default": False}) + addr.is_default = True + db.session.commit() + + return jsonify({"message": "Default address updated"}), 200 + + +@address_bp.route("/", methods=["DELETE"]) +@jwt_required() +def delete_address(address_id): + user_id = get_jwt_identity() + addr = Address.query.filter_by(id=address_id, user_id=user_id).first() + if not addr: + return jsonify({"message": "Address not found"}), 404 + + db.session.delete(addr) + db.session.commit() + return jsonify({"message": "Deleted"}), 200 + + +def serialize_address(a: Address): + return { + "id": a.id, + "label": a.label, + "line1": a.line1, + "line2": a.line2, + "city": a.city, + "state": a.state, + "zip": a.zip, + "lat": a.lat, + "lng": a.lng, + "is_default": a.is_default, + } \ No newline at end of file diff --git a/backend/app/routes.py b/backend/app/routes/auth_routes.py similarity index 90% rename from backend/app/routes.py rename to backend/app/routes/auth_routes.py index 0f69d89..b905328 100644 --- a/backend/app/routes.py +++ b/backend/app/routes/auth_routes.py @@ -1,15 +1,10 @@ from flask import Blueprint, request, jsonify from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity -from .models import User -from . import db +from ..models import User +from .. import db api = Blueprint("api", __name__) -@api.get("/api/health") -def health(): - return jsonify(status="backend running") - - @api.post("/api/auth/signup") def signup(): data = request.get_json(silent=True) or {} @@ -34,10 +29,13 @@ def signup(): @api.post("/api/auth/login") def login(): + print() data = request.get_json(silent=True) or {} email = (data.get("email") or "").strip().lower() password = data.get("password") or "" + print(f"[LOGIN] Request data: email={email}, password={'*' * len(password)}") # ✅ ADD + if not email or not password: return jsonify(message="Email and password are required."), 400 diff --git a/backend/app/routes/location_routes.py b/backend/app/routes/location_routes.py new file mode 100644 index 0000000..74595f3 --- /dev/null +++ b/backend/app/routes/location_routes.py @@ -0,0 +1,37 @@ +from flask import Blueprint, request, jsonify +from flask_jwt_extended import jwt_required, get_jwt_identity + +from .. import db +from ..models import User + +location_bp = Blueprint("location", __name__, url_prefix="/api/location") + + +@location_bp.route("/select", methods=["POST", "OPTIONS"]) + +def select_location(): + user_id = get_jwt_identity() + data = request.get_json() or {} + + label = data.get("label") + lat = data.get("lat") + lng = data.get("lng") + + if not label: + return jsonify({"message": "label is required"}), 400 + + user = User.query.get(user_id) + if not user: + return jsonify({"message": "User not found"}), 404 + + user.location_label = label + user.location_lat = lat + user.location_lng = lng + db.session.commit() + + return jsonify( + { + "message": "Location saved", + "location": {"label": user.location_label, "lat": user.location_lat, "lng": user.location_lng}, + } + ), 200 diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 3f13d3a..1d16f1f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,8 +1,10 @@ -import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; -import Home from './pages/Home'; -import Login from './pages/Login'; -import Signup from './pages/Signup'; -import ProtectedRoute from './components/ProtectedRoute'; +import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; +import Home from "./pages/Home"; +import Login from "./pages/Login"; +import Signup from "./pages/Signup"; +import ProtectedRoute from "./components/ProtectedRoute"; +import ChooseLocation from "./pages/ChooseLocation"; +import ManageAddresses from "./pages/ManageAddresses"; function App() { return ( @@ -16,6 +18,25 @@ function App() { } /> + + {/* ✅ new pages */} + + + + } + /> + + + + } + /> + } /> } /> diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 9a78ddd..65b37a3 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,14 +1,15 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import { ThemeProvider, CssBaseline } from '@mui/material'; -import App from './App'; -import theme from './theme/theme'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import { ThemeProvider } from "@mui/material/styles"; +import { CssBaseline } from "@mui/material"; +import theme from "./theme/theme"; -ReactDOM.createRoot(document.getElementById('root')!).render( +ReactDOM.createRoot(document.getElementById("root")!).render( - , + ); diff --git a/frontend/src/pages/ChooseLocation.tsx b/frontend/src/pages/ChooseLocation.tsx new file mode 100644 index 0000000..5f86c74 --- /dev/null +++ b/frontend/src/pages/ChooseLocation.tsx @@ -0,0 +1,96 @@ +// pages/ChooseLocation.tsx +import { useMemo, useState } from "react" +import { Box, Button, Container, List, ListItemButton, ListItemText, TextField, Typography } from "@mui/material" +import MyLocationIcon from "@mui/icons-material/MyLocation" +import { selectLocation } from "../services/api" +import { useNavigate } from "react-router-dom" + +type Suggestion = { label: string; lat?: number; lng?: number } + +const MOCK_SUGGESTIONS: Suggestion[] = [ + { label: "San Jose, CA", lat: 37.3382, lng: -121.8863 }, + { label: "Sunnyvale, CA", lat: 37.3688, lng: -122.0363 }, + { label: "Santa Clara, CA", lat: 37.3541, lng: -121.9552 }, + { label: "Mountain View, CA", lat: 37.3861, lng: -122.0839 }, +] + +export default function ChooseLocation() { + const navigate = useNavigate() + const [query, setQuery] = useState("") + const [loading, setLoading] = useState(false) + + const filtered = useMemo(() => { + const q = query.trim().toLowerCase() + if (!q) return MOCK_SUGGESTIONS + return MOCK_SUGGESTIONS.filter(s => s.label.toLowerCase().includes(q)) + }, [query]) + + const onPick = async (s: Suggestion) => { + setLoading(true) + try { + await selectLocation(s) + navigate("/addresses") + } finally { + setLoading(false) + } + } + + const useCurrentLocation = () => { + if (!navigator.geolocation) return alert("Geolocation not supported") + + setLoading(true) + navigator.geolocation.getCurrentPosition( + async (pos) => { + try { + const lat = pos.coords.latitude + const lng = pos.coords.longitude + // label is a placeholder. Later: reverse geocode to actual area name. + await selectLocation({ label: `Current location (${lat.toFixed(3)}, ${lng.toFixed(3)})`, lat, lng }) + navigate("/addresses") + } catch (e: any) { + alert(e?.response?.data?.message ?? "Failed to save location") + } finally { + setLoading(false) + } + }, + () => { + setLoading(false) + alert("Could not fetch location permission/coordinates") + } + ) + } + + return ( + + Choose your delivery location + + This helps us show products available near you. + + + + setQuery(e.target.value)} + /> + + + + + {filtered.map((s) => ( + onPick(s)} disabled={loading}> + + + ))} + + + ) +} diff --git a/frontend/src/pages/Home.tsx b/frontend/src/pages/Home.tsx index 73e522c..2472e4a 100644 --- a/frontend/src/pages/Home.tsx +++ b/frontend/src/pages/Home.tsx @@ -1,17 +1,11 @@ import { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { Box, Button, Typography } from '@mui/material'; -import { getHealth } from '../services/api'; function Home() { const [status, setStatus] = useState('loading...'); const navigate = useNavigate(); - useEffect(() => { - getHealth() - .then((data) => setStatus(data.status)) - .catch(() => setStatus('backend not reachable')); - }, []); const handleLogout = () => { localStorage.removeItem('token'); diff --git a/frontend/src/pages/ManageAddresses.tsx b/frontend/src/pages/ManageAddresses.tsx new file mode 100644 index 0000000..58abc07 --- /dev/null +++ b/frontend/src/pages/ManageAddresses.tsx @@ -0,0 +1,170 @@ +// pages/ManageAddresses.tsx +import { useEffect, useState } from "react" +import { + Box, Button, Chip, Container, Dialog, DialogActions, DialogContent, DialogTitle, + Divider, IconButton, List, ListItem, ListItemText, MenuItem, Stack, TextField, Typography +} from "@mui/material" +import DeleteIcon from "@mui/icons-material/Delete" +import AddIcon from "@mui/icons-material/Add" +import { createAddress, deleteAddress, getAddresses, setDefaultAddress, type AddressPayload } from "../services/api" + +type Address = AddressPayload & { id: number; is_default: boolean } + +export default function ManageAddresses() { + const [items, setItems] = useState([]) + const [loading, setLoading] = useState(false) + const [open, setOpen] = useState(false) + + const [form, setForm] = useState({ + label: "Home", + line1: "", + line2: "", + city: "", + state: "", + zip: "", + is_default: true, + }) + + const refresh = async () => { + setLoading(true) + try { + const res = await getAddresses() + setItems(res) + } finally { + setLoading(false) + } + } + + useEffect(() => { refresh() }, []) + + const onSave = async () => { + setLoading(true) + try { + await createAddress(form) + setOpen(false) + setForm({ label: "Home", line1: "", line2: "", city: "", state: "", zip: "", is_default: false }) + await refresh() + } catch (e: any) { + alert(e?.response?.data?.message ?? "Failed to create address") + } finally { + setLoading(false) + } + } + + const onMakeDefault = async (id: number) => { + setLoading(true) + try { + await setDefaultAddress(id) + await refresh() + } finally { + setLoading(false) + } + } + + const onDelete = async (id: number) => { + if (!confirm("Delete this address?")) return + setLoading(true) + try { + await deleteAddress(id) + await refresh() + } finally { + setLoading(false) + } + } + + return ( + + + + Your addresses + + Add at least one address to checkout faster. + + + + + + + {items.length === 0 && ( + + No addresses yet. + + )} + + {items.map((a, idx) => ( + + + {!a.is_default && ( + + )} + onDelete(a.id)} disabled={loading}> + + + + } + > + + {a.label} + {a.is_default && } + + } + secondary={`${a.line1}${a.line2 ? ", " + a.line2 : ""}, ${a.city}, ${a.state} ${a.zip}`} + /> + + {idx !== items.length - 1 && } + + ))} + + + {/* Add Address Dialog */} + setOpen(false)} fullWidth maxWidth="sm"> + Add new address + + + setForm(f => ({ ...f, label: e.target.value }))} + > + Home + Work + Other + + + setForm(f => ({ ...f, line1: e.target.value }))} /> + setForm(f => ({ ...f, line2: e.target.value }))} /> + + setForm(f => ({ ...f, city: e.target.value }))} /> + setForm(f => ({ ...f, state: e.target.value }))} /> + + setForm(f => ({ ...f, zip: e.target.value }))} /> + + + setForm(f => ({ ...f, is_default: !f.is_default }))} + /> + + Tap chip to toggle default + + + + + + + + + + + ) +} diff --git a/frontend/src/services/api.ts b/frontend/src/services/api.ts index 1a45ccf..7cb0389 100644 --- a/frontend/src/services/api.ts +++ b/frontend/src/services/api.ts @@ -7,6 +7,7 @@ export const api = axios.create({ headers: { 'Content-Type': 'application/json', }, + // withCredentials: true, }); //Automatically attach JWT to every request (if present) @@ -19,12 +20,6 @@ api.interceptors.request.use((config) => { return config; }); -//Health check (public) -export async function getHealth() { - const res = await api.get('/health'); - return res.data; -} - //Auth APIs export async function login(email: string, password: string) { @@ -42,3 +37,54 @@ export async function me() { const res = await api.get('/me'); return res.data; } + +// Location APIs +export type LocationPayload = { + label: string; + lat?: number; + lng?: number; +}; + +export async function selectLocation(payload: LocationPayload) { + const res = await api.post("/location/select", payload); + return res.data; // { message, location } +} + +// Address APIs +export type AddressPayload = { + label: "Home" | "Work" | "Other" | string; + line1: string; + line2?: string; + city: string; + state: string; + zip: string; + lat?: number; + lng?: number; + is_default?: boolean; +}; + +export type Address = AddressPayload & { + id: number; + is_default: boolean; + created_at?: string; +}; + +export async function getAddresses() { + const res = await api.get("/addresses"); + return res.data as Address[]; +} + +export async function createAddress(payload: AddressPayload) { + const res = await api.post("/addresses", payload); + return res.data as Address; +} + +export async function setDefaultAddress(addressId: number) { + const res = await api.patch(`/addresses/${addressId}/default`); + return res.data; // { message } +} + +export async function deleteAddress(addressId: number) { + const res = await api.delete(`/addresses/${addressId}`); + return res.data; // { message } +} diff --git a/frontend/src/theme/theme.ts b/frontend/src/theme/theme.ts index 3d73b5d..115e935 100644 --- a/frontend/src/theme/theme.ts +++ b/frontend/src/theme/theme.ts @@ -1,17 +1,86 @@ -import { createTheme } from '@mui/material/styles'; +import { createTheme } from "@mui/material/styles"; const theme = createTheme({ palette: { - mode: 'light', + mode: "light", + + // Mint primary primary: { - main: '#2e7d32', + main: "#39B68B", + contrastText: "#FFFFFF", + }, + + // Lavender accent + secondary: { + main: "#7C74D8", }, + success: { - main: '#2e7d32', + main: "#39B68B", + }, + + background: { + default: "#EAF7F1", // soft mint background + paper: "#FFFFFF", + }, + + text: { + primary: "#2D2A4A", + secondary: "#5B5875", }, }, + + // Rounded, modern feel shape: { - borderRadius: 10, + borderRadius: 16, + }, + + typography: { + fontFamily: `"Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial`, + h5: { + fontWeight: 800, + }, + button: { + textTransform: "none", + fontWeight: 700, + }, + }, + + components: { + MuiButton: { + styleOverrides: { + root: { + borderRadius: 12, + height: 44, + paddingInline: 16, + }, + containedPrimary: { + boxShadow: "none", + }, + }, + }, + + MuiPaper: { + styleOverrides: { + root: { + borderRadius: 16, + }, + }, + }, + + MuiTextField: { + defaultProps: { + size: "small", + }, + }, + + MuiListItemButton: { + styleOverrides: { + root: { + borderRadius: 14, + }, + }, + }, }, });