From 852bd972be91fa8fd9f89b63118d39ae142f7bab Mon Sep 17 00:00:00 2001 From: Juanjo Villarejo Cespedes Date: Sun, 2 Nov 2025 08:35:08 +0000 Subject: [PATCH 01/17] instalando librerias back-end --- package-lock.json | 56 +++++++++++++++++++---------------------------- src/app.py | 13 +++++++++++ 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8d43d98ab7..44f1ece2dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,6 +73,7 @@ "integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -884,7 +885,6 @@ "dev": true, "license": "MIT", "optional": true, - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -997,14 +997,6 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/node": { - "version": "16.11.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", - "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", @@ -1018,6 +1010,7 @@ "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1066,6 +1059,7 @@ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1294,6 +1288,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -1312,8 +1307,7 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "node_modules/call-bind": { "version": "1.0.8", @@ -1797,6 +1791,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -3486,6 +3481,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -3498,6 +3494,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -3921,7 +3918,6 @@ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "optional": true, - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -3933,7 +3929,6 @@ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, "optional": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -4081,7 +4076,6 @@ "dev": true, "license": "BSD-2-Clause", "optional": true, - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -4100,8 +4094,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "node_modules/text-table": { "version": "0.2.0", @@ -4278,6 +4271,7 @@ "integrity": "sha512-qK9W4xjgD3gXbC0NmdNFFnVFLMWSNiR3swj957yutwzzN16xF/E7nmtAyp1rT9hviDroQANjE4HK3H4WqWdFtw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.18.10", "postcss": "^8.4.27", @@ -4500,6 +4494,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.7.tgz", "integrity": "sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA==", "dev": true, + "peer": true, "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -4950,7 +4945,6 @@ "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", "dev": true, "optional": true, - "peer": true, "requires": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -5044,14 +5038,6 @@ "@babel/types": "^7.20.7" } }, - "@types/node": { - "version": "16.11.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", - "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==", - "dev": true, - "optional": true, - "peer": true - }, "@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", @@ -5063,6 +5049,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", "dev": true, + "peer": true, "requires": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -5098,7 +5085,8 @@ "version": "8.14.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", - "dev": true + "dev": true, + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -5245,6 +5233,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", "dev": true, + "peer": true, "requires": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -5257,8 +5246,7 @@ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "dev": true, - "optional": true, - "peer": true + "optional": true }, "call-bind": { "version": "1.0.8", @@ -5600,6 +5588,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, + "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -6702,6 +6691,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "peer": true, "requires": { "loose-envify": "^1.1.0" } @@ -6710,6 +6700,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "peer": true, "requires": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -6988,7 +6979,6 @@ "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "optional": true, - "peer": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -6999,8 +6989,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "optional": true, - "peer": true + "optional": true } } }, @@ -7100,7 +7089,6 @@ "integrity": "sha512-GWANVlPM/ZfYzuPHjq0nxT+EbOEDDN3Jwhwdg1D8TU8oSkktp8w64Uq4auuGLxFSoNTRDncTq2hQHX1Ld9KHkA==", "dev": true, "optional": true, - "peer": true, "requires": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -7113,8 +7101,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, - "optional": true, - "peer": true + "optional": true } } }, @@ -7228,6 +7215,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.9.tgz", "integrity": "sha512-qK9W4xjgD3gXbC0NmdNFFnVFLMWSNiR3swj957yutwzzN16xF/E7nmtAyp1rT9hviDroQANjE4HK3H4WqWdFtw==", "dev": true, + "peer": true, "requires": { "esbuild": "^0.18.10", "fsevents": "~2.3.2", diff --git a/src/app.py b/src/app.py index 1b3340c0fa..bc26fb7eec 100644 --- a/src/app.py +++ b/src/app.py @@ -11,6 +11,13 @@ from api.admin import setup_admin from api.commands import setup_commands +from flask_jwt_extended import create_access_token +from flask_jwt_extended import get_jwt_identity +from flask_jwt_extended import jwt_required +from flask_jwt_extended import JWTManager + +from flask_bcrypt import Bcrypt + # from models import Person ENV = "development" if os.getenv("FLASK_DEBUG") == "1" else "production" @@ -19,6 +26,12 @@ app = Flask(__name__) app.url_map.strict_slashes = False +# Setup the Flask-JWT-Extended extension +app.config["JWT_SECRET_KEY"] = "ESTA_ES_NUESTRA_LLAVE" +jwt = JWTManager(app) + +bcrypt = Bcrypt(app) + # database condiguration db_url = os.getenv("DATABASE_URL") if db_url is not None: From ce5784aa47510dabae8b60f8589acd4b62ab4eb9 Mon Sep 17 00:00:00 2001 From: Juanjo Villarejo Cespedes Date: Mon, 3 Nov 2025 08:41:17 +0000 Subject: [PATCH 02/17] importado User --- src/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app.py b/src/app.py index bc26fb7eec..9a7a8b7a5f 100644 --- a/src/app.py +++ b/src/app.py @@ -6,7 +6,7 @@ from flask_migrate import Migrate from flask_swagger import swagger from api.utils import APIException, generate_sitemap -from api.models import db +from api.models import db, User from api.routes import api from api.admin import setup_admin from api.commands import setup_commands From dcdf09002f2a41a011b8f11db925d17285b9c8d9 Mon Sep 17 00:00:00 2001 From: Juanjo Villarejo Cespedes Date: Mon, 3 Nov 2025 11:50:23 +0000 Subject: [PATCH 03/17] tabla models User Activity Message --- src/api/models.py | 118 ++++++++++++++++++++++++++++++++++++++++++---- src/app.py | 2 +- 2 files changed, 111 insertions(+), 9 deletions(-) diff --git a/src/api/models.py b/src/api/models.py index da515f6a1a..2813c18e17 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -1,19 +1,121 @@ +from datetime import datetime, timezone from flask_sqlalchemy import SQLAlchemy -from sqlalchemy import String, Boolean -from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy import String, Float, DateTime, Text, Date, Time, Integer, ForeignKey, Table +from sqlalchemy.orm import mapped_column, relationship +from werkzeug.security import generate_password_hash, check_password_hash db = SQLAlchemy() +# Tabla intermedia (relación muchos a muchos) +activity_user = Table( + "activity_user", + db.Model.metadata, + db.Column("id", Integer, primary_key=True), + db.Column("user_id", Integer, ForeignKey("user.id")), + db.Column("activity_id", Integer, ForeignKey("activity.id")), + db.Column("joined_at", DateTime, default=lambda: datetime.now(timezone.utc)) +) + +# MODELO: User class User(db.Model): - id: Mapped[int] = mapped_column(primary_key=True) - email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) - password: Mapped[str] = mapped_column(nullable=False) - is_active: Mapped[bool] = mapped_column(Boolean(), nullable=False) + __tablename__ = "user" + + id = mapped_column(Integer, primary_key=True) + name = mapped_column(String(50), nullable=False) + email = mapped_column(String(120), unique=True, nullable=False) + password_hash = mapped_column(String(200), nullable=False) + avatar_url = mapped_column(String(200)) + biography = mapped_column(Text) + sports = mapped_column(String(200)) + level = mapped_column(String(20)) + latitude = mapped_column(Float) + longitude = mapped_column(Float) + created_at = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc)) + + # Relaciones + activities_created = relationship("Activity", back_populates="creator", lazy=True) + activities_joined = relationship("Activity", secondary=activity_user, back_populates="participants") + messages_sent = relationship("Message", back_populates="sender", lazy=True) + # Métodos de seguridad + def set_password(self, password): + self.password_hash = generate_password_hash(password) + + def check_password(self, password): + return check_password_hash(self.password_hash, password) def serialize(self): return { "id": self.id, + "name": self.name, "email": self.email, - # do not serialize the password, its a security breach - } \ No newline at end of file + "avatar_url": self.avatar_url, + "biography": self.biography, + "sports": self.sports, + "level": self.level, + "latitude": self.latitude, + "longitude": self.longitude, + "created_at": self.created_at.isoformat(), + } + + +# MODELO: Activity +class Activity(db.Model): + __tablename__ = "activity" + + id = mapped_column(Integer, primary_key=True) + title = mapped_column(String(100), nullable=False) + sport = mapped_column(String(50), nullable=False) + description = mapped_column(Text) + latitude = mapped_column(Float, nullable=False) + longitude = mapped_column(Float, nullable=False) + date = mapped_column(Date, nullable=False) + time = mapped_column(Time, nullable=False) + created_by = mapped_column(Integer, ForeignKey("user.id")) + max_participants = mapped_column(Integer) + created_at = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc)) + + # Relaciones + creator = relationship("User", back_populates="activities_created") + participants = relationship("User", secondary=activity_user, back_populates="activities_joined") + messages = relationship("Message", back_populates="activity", lazy=True) + + def serialize(self): + return { + "id": self.id, + "title": self.title, + "sport": self.sport, + "description": self.description, + "latitude": self.latitude, + "longitude": self.longitude, + "date": str(self.date), + "time": str(self.time), + "created_by": self.created_by, + "creator_name": self.creator.name if self.creator else None, + "max_participants": self.max_participants, + "created_at": self.created_at.isoformat(), + "participants": [p.id for p in self.participants], + } + + +# MODELO: Message +class Message(db.Model): + __tablename__ = "message" + + id = mapped_column(Integer, primary_key=True) + activity_id = mapped_column(Integer, ForeignKey("activity.id")) + sender_id = mapped_column(Integer, ForeignKey("user.id")) + message = mapped_column(Text, nullable=False) + created_at = mapped_column(DateTime, default=lambda: datetime.now(timezone.utc)) + + sender = relationship("User", back_populates="messages_sent") + activity = relationship("Activity", back_populates="messages") + + def serialize(self): + return { + "id": self.id, + "activity_id": self.activity_id, + "sender_id": self.sender_id, + "message": self.message, + "created_at": self.created_at.isoformat(), + } diff --git a/src/app.py b/src/app.py index 9a7a8b7a5f..7999682690 100644 --- a/src/app.py +++ b/src/app.py @@ -6,7 +6,7 @@ from flask_migrate import Migrate from flask_swagger import swagger from api.utils import APIException, generate_sitemap -from api.models import db, User +from api.models import db, User, Activity, Message from api.routes import api from api.admin import setup_admin from api.commands import setup_commands From 4fd57e5c46a6a4165f17affe8b07493b843cdfe8 Mon Sep 17 00:00:00 2001 From: Juanjo Villarejo Cespedes Date: Tue, 4 Nov 2025 12:59:08 +0000 Subject: [PATCH 04/17] password gmail --- Pipfile | 1 + Pipfile.lock | 184 +++++++++++++++++++++++++++++++-------------------- src/app.py | 14 ++++ 3 files changed, 126 insertions(+), 73 deletions(-) diff --git a/Pipfile b/Pipfile index 44e04f14ff..3d2074fb0c 100644 --- a/Pipfile +++ b/Pipfile @@ -20,6 +20,7 @@ typing-extensions = "*" flask-jwt-extended = "==4.6.0" wtforms = "==3.1.2" sqlalchemy = "*" +flask-mail = "*" [requires] python_version = "3.13" diff --git a/Pipfile.lock b/Pipfile.lock index b201c3decc..3275b61289 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "d2e672e650278aeeee2fe49bd76d76497d8b65a50f8b5dbb121d265cbc6ef4e5" + "sha256": "3f984adbcb33f8bdadc9347099a43d8924f246a3077b42cc7f4acfc3b9b80596" }, "pipfile-spec": 6, "requires": { @@ -42,11 +42,11 @@ }, "click": { "hashes": [ - "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", - "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a" + "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", + "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4" ], - "markers": "python_version >= '3.7'", - "version": "==8.1.8" + "markers": "python_version >= '3.10'", + "version": "==8.3.0" }, "cloudinary": { "hashes": [ @@ -58,11 +58,12 @@ }, "flask": { "hashes": [ - "sha256:5f873c5184c897c8d9d1b05df1e3d01b14910ce69607a117bd3277098a5836ac", - "sha256:d667207822eb83f1c4b50949b1623c8fc8d51f2341d65f72e1a1815397551136" + "sha256:bf656c15c80190ed628ad08cdfd3aaa35beb087855e2f494910aa3774cc4fd87", + "sha256:ca1d8112ec8a6158cc29ea4858963350011b5c846a414cdb7a954aa9e967d03c" ], "index": "pypi", - "version": "==3.1.0" + "markers": "python_version >= '3.9'", + "version": "==3.1.2" }, "flask-admin": { "hashes": [ @@ -88,6 +89,15 @@ "index": "pypi", "version": "==4.6.0" }, + "flask-mail": { + "hashes": [ + "sha256:44083e7b02bbcce792209c06252f8569dd5a325a7aaa76afe7330422bd97881d", + "sha256:a451e490931bb3441d9b11ebab6812a16bfa81855792ae1bf9c1e1e22c4e51e7" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.10.0" + }, "flask-migrate": { "hashes": [ "sha256:1a336b06eb2c3ace005f5f2ded8641d534c18798d64061f6ff11f79e1434126d", @@ -209,11 +219,11 @@ }, "jinja2": { "hashes": [ - "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb", - "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb" + "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", + "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67" ], "markers": "python_version >= '3.7'", - "version": "==3.1.5" + "version": "==3.1.6" }, "mako": { "hashes": [ @@ -225,70 +235,98 @@ }, "markupsafe": { "hashes": [ - "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", - "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", - "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0", - "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", - "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", - "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13", - "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", - "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", - "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", - "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", - "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0", - "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b", - "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579", - "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", - "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", - "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff", - "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", - "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", - "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", - "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb", - "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", - "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", - "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a", - "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", - "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a", - "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", - "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8", - "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", - "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", - "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144", - "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f", - "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", - "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", - "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", - "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", - "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158", - "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", - "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", - "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", - "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171", - "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", - "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", - "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", - "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d", - "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", - "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", - "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", - "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", - "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29", - "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", - "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", - "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c", - "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", - "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", - "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", - "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a", - "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178", - "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", - "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", - "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", - "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50" + "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", + "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", + "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", + "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", + "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", + "sha256:0f4b68347f8c5eab4a13419215bdfd7f8c9b19f2b25520968adfad23eb0ce60c", + "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", + "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", + "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", + "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", + "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", + "sha256:15d939a21d546304880945ca1ecb8a039db6b4dc49b2c5a400387cdae6a62e26", + "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", + "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", + "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", + "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", + "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", + "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", + "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", + "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", + "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", + "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", + "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", + "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", + "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", + "sha256:3537e01efc9d4dccdf77221fb1cb3b8e1a38d5428920e0657ce299b20324d758", + "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", + "sha256:38664109c14ffc9e7437e86b4dceb442b0096dfe3541d7864d9cbe1da4cf36c8", + "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", + "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", + "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", + "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", + "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", + "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", + "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", + "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", + "sha256:591ae9f2a647529ca990bc681daebdd52c8791ff06c2bfa05b65163e28102ef2", + "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", + "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", + "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", + "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", + "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", + "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", + "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", + "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", + "sha256:7c3fb7d25180895632e5d3148dbdc29ea38ccb7fd210aa27acbd1201a1902c6e", + "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", + "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", + "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", + "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", + "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", + "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", + "sha256:949b8d66bc381ee8b007cd945914c721d9aba8e27f71959d750a46f7c282b20b", + "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", + "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", + "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", + "sha256:a320721ab5a1aba0a233739394eb907f8c8da5c98c9181d1161e77a0c8e36f2d", + "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", + "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", + "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", + "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", + "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", + "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", + "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", + "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", + "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", + "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", + "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", + "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", + "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", + "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", + "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", + "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", + "sha256:df2449253ef108a379b8b5d6b43f4b1a8e81a061d6537becd5582fba5f9196d7", + "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", + "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", + "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", + "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", + "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", + "sha256:e8fc20152abba6b83724d7ff268c249fa196d8259ff481f3b1476383f8f24e42", + "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", + "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", + "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", + "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", + "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", + "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", + "sha256:f71a396b3bf33ecaa1626c255855702aca4d3d9fea5e051b41ac59a9c1c41edc", + "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", + "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50" ], "markers": "python_version >= '3.9'", - "version": "==3.0.2" + "version": "==3.0.3" }, "packaging": { "hashes": [ diff --git a/src/app.py b/src/app.py index 7999682690..8003a91671 100644 --- a/src/app.py +++ b/src/app.py @@ -18,6 +18,8 @@ from flask_bcrypt import Bcrypt +from flask_mail import Mail + # from models import Person ENV = "development" if os.getenv("FLASK_DEBUG") == "1" else "production" @@ -32,6 +34,18 @@ bcrypt = Bcrypt(app) +app.config.update(dict( + DEBUG=False, + MAIL_SERVER='smtp.gmail.com', + MAIL_PORT=587, + MAIL_USE_TLS=True, + MAIL_USE_SSL=False, + MAIL_USERNAME='meetfit119@gmail.com', + MAIL_PASSWORD=os.getenv('MAIL_PASSWORD') + +)) + + # database condiguration db_url = os.getenv("DATABASE_URL") if db_url is not None: From 1232afa0d0ae435a3217c76a62833c2272bbb06d Mon Sep 17 00:00:00 2001 From: Juanjo Villarejo Cespedes Date: Tue, 4 Nov 2025 13:24:47 +0000 Subject: [PATCH 05/17] prueba exito enviar correo end point --- src/app.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/app.py b/src/app.py index 8003a91671..0780b28012 100644 --- a/src/app.py +++ b/src/app.py @@ -6,19 +6,12 @@ from flask_migrate import Migrate from flask_swagger import swagger from api.utils import APIException, generate_sitemap -from api.models import db, User, Activity, Message +from api.models import db from api.routes import api from api.admin import setup_admin from api.commands import setup_commands -from flask_jwt_extended import create_access_token -from flask_jwt_extended import get_jwt_identity -from flask_jwt_extended import jwt_required -from flask_jwt_extended import JWTManager - -from flask_bcrypt import Bcrypt - -from flask_mail import Mail +from flask_mail import Mail, Message # from models import Person @@ -30,9 +23,7 @@ # Setup the Flask-JWT-Extended extension app.config["JWT_SECRET_KEY"] = "ESTA_ES_NUESTRA_LLAVE" -jwt = JWTManager(app) -bcrypt = Bcrypt(app) app.config.update(dict( DEBUG=False, @@ -40,11 +31,11 @@ MAIL_PORT=587, MAIL_USE_TLS=True, MAIL_USE_SSL=False, - MAIL_USERNAME='meetfit119@gmail.com', + MAIL_USERNAME='meetfitfspt119@gmail.com', MAIL_PASSWORD=os.getenv('MAIL_PASSWORD') - )) +mail = Mail(app) # database condiguration db_url = os.getenv("DATABASE_URL") @@ -93,6 +84,19 @@ def serve_any_other_file(path): return response +#prueba send-mail +@app.route('/api/send-mail', methods=['GET']) +def send_mail(): + msg = Message( + subject='Prueba de correo de proyecto', + sender='meetfitfspt119@gmail.com', + recipients=['meetfitfspt119@gmail.com'], + ) + msg.html = '

Testeando envio de correo

' + mail.send(msg) + return jsonify({'msg': 'Correo enviado con exito'}), 200 + + # this only runs if `$ python src/main.py` is executed if __name__ == '__main__': PORT = int(os.environ.get('PORT', 3001)) From 1b7c9aba1b475874ec9541f347b0e712a43e5091 Mon Sep 17 00:00:00 2001 From: Juanjo Villarejo Cespedes Date: Wed, 5 Nov 2025 12:21:27 +0000 Subject: [PATCH 06/17] actualizando models --- src/api/models.py | 40 ++++++++++++++++++++++++++++++++++++++-- src/app.py | 2 +- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/api/models.py b/src/api/models.py index 2813c18e17..68eac0dba0 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -1,6 +1,7 @@ -from datetime import datetime, timezone +from datetime import datetime, timezone, timedelta +import secrets from flask_sqlalchemy import SQLAlchemy -from sqlalchemy import String, Float, DateTime, Text, Date, Time, Integer, ForeignKey, Table +from sqlalchemy import (String, Float, DateTime, Text, Date, Time, Integer, ForeignKey, Table, Boolean) from sqlalchemy.orm import mapped_column, relationship from werkzeug.security import generate_password_hash, check_password_hash @@ -36,6 +37,7 @@ class User(db.Model): activities_created = relationship("Activity", back_populates="creator", lazy=True) activities_joined = relationship("Activity", secondary=activity_user, back_populates="participants") messages_sent = relationship("Message", back_populates="sender", lazy=True) + reset_tokens = relationship("PasswordResetToken", back_populates="user", cascade="all, delete-orphan") # Métodos de seguridad def set_password(self, password): @@ -119,3 +121,37 @@ def serialize(self): "message": self.message, "created_at": self.created_at.isoformat(), } + +# MODELO: PasswordResetToken +class PasswordResetToken(db.Model): + __tablename__ = "password_reset_token" + + id = mapped_column(Integer, primary_key=True) + user_id = mapped_column(Integer, ForeignKey("user.id", ondelete="CASCADE"), nullable=False) + token = mapped_column(String(128), unique=True, nullable=False) + expires_at = mapped_column(DateTime(timezone=True), nullable=False) + used = mapped_column(Boolean, default=False) + created_at = mapped_column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)) + + user = relationship("User", back_populates="reset_tokens") + + @staticmethod + def generate_token(user_id, expiration_minutes=30): + """Genera un nuevo token de recuperación con validez temporal.""" + token = secrets.token_urlsafe(64) + expires_at = datetime.now(timezone.utc) + timedelta(minutes=expiration_minutes) + return PasswordResetToken(user_id=user_id, token=token, expires_at=expires_at) + + def is_valid(self): + """Valida si el token sigue activo y no se ha usado.""" + return not self.used and datetime.now(timezone.utc) < self.expires_at + + def serialize(self): + return { + "id": self.id, + "user_id": self.user_id, + "token": self.token, + "expires_at": self.expires_at.isoformat(), + "used": self.used, + "created_at": self.created_at.isoformat(), + } diff --git a/src/app.py b/src/app.py index 0780b28012..88525d99bb 100644 --- a/src/app.py +++ b/src/app.py @@ -6,7 +6,7 @@ from flask_migrate import Migrate from flask_swagger import swagger from api.utils import APIException, generate_sitemap -from api.models import db +from api.models import db, User from api.routes import api from api.admin import setup_admin from api.commands import setup_commands From 2a4ca0d45e178cc38b9fb0e3c83f4aae66de7e42 Mon Sep 17 00:00:00 2001 From: Juanjo Villarejo Cespedes Date: Wed, 5 Nov 2025 12:29:28 +0000 Subject: [PATCH 07/17] revisando models --- Pipfile | 1 + Pipfile.lock | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/app.py | 12 +++++++- 3 files changed, 90 insertions(+), 2 deletions(-) diff --git a/Pipfile b/Pipfile index 3d2074fb0c..603261a952 100644 --- a/Pipfile +++ b/Pipfile @@ -21,6 +21,7 @@ flask-jwt-extended = "==4.6.0" wtforms = "==3.1.2" sqlalchemy = "*" flask-mail = "*" +flask-bcrypt = "*" [requires] python_version = "3.13" diff --git a/Pipfile.lock b/Pipfile.lock index 3275b61289..df46d983e7 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "3f984adbcb33f8bdadc9347099a43d8924f246a3077b42cc7f4acfc3b9b80596" + "sha256": "ffbb99bdc7f9b5365cbac326bac4ed80e2f0f9a8758a61f0407d2983663030a3" }, "pipfile-spec": 6, "requires": { @@ -24,6 +24,75 @@ "markers": "python_version >= '3.8'", "version": "==1.14.1" }, + "bcrypt": { + "hashes": [ + "sha256:046ad6db88edb3c5ece4369af997938fb1c19d6a699b9c1b27b0db432faae4c4", + "sha256:0c418ca99fd47e9c59a301744d63328f17798b5947b0f791e9af3c1c499c2d0a", + "sha256:0c8e093ea2532601a6f686edbc2c6b2ec24131ff5c52f7610dd64fa4553b5464", + "sha256:0cae4cb350934dfd74c020525eeae0a5f79257e8a201c0c176f4b84fdbf2a4b4", + "sha256:137c5156524328a24b9fac1cb5db0ba618bc97d11970b39184c1d87dc4bf1746", + "sha256:200af71bc25f22006f4069060c88ed36f8aa4ff7f53e67ff04d2ab3f1e79a5b2", + "sha256:212139484ab3207b1f0c00633d3be92fef3c5f0af17cad155679d03ff2ee1e41", + "sha256:2b732e7d388fa22d48920baa267ba5d97cca38070b69c0e2d37087b381c681fd", + "sha256:35a77ec55b541e5e583eb3436ffbbf53b0ffa1fa16ca6782279daf95d146dcd9", + "sha256:38cac74101777a6a7d3b3e3cfefa57089b5ada650dce2baf0cbdd9d65db22a9e", + "sha256:3abeb543874b2c0524ff40c57a4e14e5d3a66ff33fb423529c88f180fd756538", + "sha256:3ca8a166b1140436e058298a34d88032ab62f15aae1c598580333dc21d27ef10", + "sha256:3cf67a804fc66fc217e6914a5635000259fbbbb12e78a99488e4d5ba445a71eb", + "sha256:4870a52610537037adb382444fefd3706d96d663ac44cbb2f37e3919dca3d7ef", + "sha256:48f753100931605686f74e27a7b49238122aa761a9aefe9373265b8b7aa43ea4", + "sha256:4bfd2a34de661f34d0bda43c3e4e79df586e4716ef401fe31ea39d69d581ef23", + "sha256:560ddb6ec730386e7b3b26b8b4c88197aaed924430e7b74666a586ac997249ef", + "sha256:5b1589f4839a0899c146e8892efe320c0fa096568abd9b95593efac50a87cb75", + "sha256:5feebf85a9cefda32966d8171f5db7e3ba964b77fdfe31919622256f80f9cf42", + "sha256:611f0a17aa4a25a69362dcc299fda5c8a3d4f160e2abb3831041feb77393a14a", + "sha256:61afc381250c3182d9078551e3ac3a41da14154fbff647ddf52a769f588c4172", + "sha256:64d7ce196203e468c457c37ec22390f1a61c85c6f0b8160fd752940ccfb3a683", + "sha256:64ee8434b0da054d830fa8e89e1c8bf30061d539044a39524ff7dec90481e5c2", + "sha256:6b8f520b61e8781efee73cba14e3e8c9556ccfb375623f4f97429544734545b4", + "sha256:741449132f64b3524e95cd30e5cd3343006ce146088f074f31ab26b94e6c75ba", + "sha256:744d3c6b164caa658adcb72cb8cc9ad9b4b75c7db507ab4bc2480474a51989da", + "sha256:79cfa161eda8d2ddf29acad370356b47f02387153b11d46042e93a0a95127493", + "sha256:7aeef54b60ceddb6f30ee3db090351ecf0d40ec6e2abf41430997407a46d2254", + "sha256:7edda91d5ab52b15636d9c30da87d2cc84f426c72b9dba7a9b4fe142ba11f534", + "sha256:7f277a4b3390ab4bebe597800a90da0edae882c6196d3038a73adf446c4f969f", + "sha256:7f4c94dec1b5ab5d522750cb059bb9409ea8872d4494fd152b53cca99f1ddd8c", + "sha256:801cad5ccb6b87d1b430f183269b94c24f248dddbbc5c1f78b6ed231743e001c", + "sha256:83e787d7a84dbbfba6f250dd7a5efd689e935f03dd83b0f919d39349e1f23f83", + "sha256:89042e61b5e808b67daf24a434d89bab164d4de1746b37a8d173b6b14f3db9ff", + "sha256:92864f54fb48b4c718fc92a32825d0e42265a627f956bc0361fe869f1adc3e7d", + "sha256:9d52ed507c2488eddd6a95bccee4e808d3234fa78dd370e24bac65a21212b861", + "sha256:9fffdb387abe6aa775af36ef16f55e318dcda4194ddbf82007a6f21da29de8f5", + "sha256:a28bc05039bdf3289d757f49d616ab3efe8cf40d8e8001ccdd621cd4f98f4fc9", + "sha256:a5393eae5722bcef046a990b84dff02b954904c36a194f6cfc817d7dca6c6f0b", + "sha256:a71f70ee269671460b37a449f5ff26982a6f2ba493b3eabdd687b4bf35f875ac", + "sha256:b17366316c654e1ad0306a6858e189fc835eca39f7eb2cafd6aaca8ce0c40a2e", + "sha256:baade0a5657654c2984468efb7d6c110db87ea63ef5a4b54732e7e337253e44f", + "sha256:c2388ca94ffee269b6038d48747f4ce8df0ffbea43f31abfa18ac72f0218effb", + "sha256:c58b56cdfb03202b3bcc9fd8daee8e8e9b6d7e3163aa97c631dfcfcc24d36c86", + "sha256:cde08734f12c6a4e28dc6755cd11d3bdfea608d93d958fffbe95a7026ebe4980", + "sha256:d79e5c65dcc9af213594d6f7f1fa2c98ad3fc10431e7aa53c176b441943efbdd", + "sha256:d8d65b564ec849643d9f7ea05c6d9f0cd7ca23bdd4ac0c2dbef1104ab504543d", + "sha256:db99dca3b1fdc3db87d7c57eac0c82281242d1eabf19dcb8a6b10eb29a2e72d1", + "sha256:dcd58e2b3a908b5ecc9b9df2f0085592506ac2d5110786018ee5e160f28e0911", + "sha256:dd19cf5184a90c873009244586396a6a884d591a5323f0e8a5922560718d4993", + "sha256:ddb4e1500f6efdd402218ffe34d040a1196c072e07929b9820f363a1fd1f4191", + "sha256:e3cf5b2560c7b5a142286f69bde914494b6d8f901aaa71e453078388a50881c4", + "sha256:ed2e1365e31fc73f1825fa830f1c8f8917ca1b3ca6185773b349c20fd606cec2", + "sha256:edfcdcedd0d0f05850c52ba3127b1fce70b9f89e0fe5ff16517df7e81fa3cbb8", + "sha256:f0ce778135f60799d89c9693b9b398819d15f1921ba15fe719acb3178215a7db", + "sha256:f2347d3534e76bf50bca5500989d6c1d05ed64b440408057a37673282c654927", + "sha256:f3c08197f3039bec79cee59a606d62b96b16669cff3949f21e74796b6e3cd2be", + "sha256:f632fd56fc4e61564f78b46a2269153122db34988e78b6be8b32d28507b7eaeb", + "sha256:f6984a24db30548fd39a44360532898c33528b74aedf81c26cf29c51ee47057e", + "sha256:f70aadb7a809305226daedf75d90379c397b094755a710d7014b8b117df1ebbf", + "sha256:f748f7c2d6fd375cc93d3fba7ef4a9e3a092421b8dbf34d8d4dc06be9492dfdd", + "sha256:f8429e1c410b4073944f03bd778a9e066e7fad723564a52ff91841d278dfc822", + "sha256:fc746432b951e92b58317af8e0ca746efe93e66555f1b40888865ef5bf56446b" + ], + "markers": "python_version >= '3.8'", + "version": "==5.0.0" + }, "blinker": { "hashes": [ "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", @@ -73,6 +142,14 @@ "index": "pypi", "version": "==1.6.1" }, + "flask-bcrypt": { + "hashes": [ + "sha256:062fd991dc9118d05ac0583675507b9fe4670e44416c97e0e6819d03d01f808a", + "sha256:f07b66b811417ea64eb188ae6455b0b708a793d966e1a80ceec4a23bc42a4369" + ], + "index": "pypi", + "version": "==1.0.1" + }, "flask-cors": { "hashes": [ "sha256:6ccb38d16d6b72bbc156c1c3f192bc435bfcc3c2bc864b2df1eb9b2d97b2403c", diff --git a/src/app.py b/src/app.py index 88525d99bb..c1a2b9bc37 100644 --- a/src/app.py +++ b/src/app.py @@ -6,11 +6,18 @@ from flask_migrate import Migrate from flask_swagger import swagger from api.utils import APIException, generate_sitemap -from api.models import db, User +from api.models import db, User, Activity, Message, PasswordResetToken from api.routes import api from api.admin import setup_admin from api.commands import setup_commands +from flask_jwt_extended import create_access_token +from flask_jwt_extended import get_jwt_identity +from flask_jwt_extended import jwt_required +from flask_jwt_extended import JWTManager + +from flask_bcrypt import Bcrypt + from flask_mail import Mail, Message # from models import Person @@ -24,6 +31,9 @@ # Setup the Flask-JWT-Extended extension app.config["JWT_SECRET_KEY"] = "ESTA_ES_NUESTRA_LLAVE" +jwt = JWTManager(app) + +bcrypt = Bcrypt(app) app.config.update(dict( DEBUG=False, From be30472ebb0751b4527afb40b67f9f1185031056 Mon Sep 17 00:00:00 2001 From: Sergioalv15 Date: Wed, 5 Nov 2025 21:21:11 +0000 Subject: [PATCH 08/17] [Tareas BACKEND-LOGIN / REGISTER / USER listas] --- src/app.py | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/src/app.py b/src/app.py index c1a2b9bc37..780309ea9b 100644 --- a/src/app.py +++ b/src/app.py @@ -106,6 +106,76 @@ def send_mail(): mail.send(msg) return jsonify({'msg': 'Correo enviado con exito'}), 200 +@app.route('/api/register', methods=['POST']) +def register(): + body = request.get_json(silent=True) + + if body is None: + return jsonify({'msg': 'POST method needs a body or email/password not found.'}), 400 + if 'email' not in body: + return jsonify({'msg': 'email field is mandatory'}), 400 + if 'password' not in body: + return jsonify({'msg': 'password field is mandatory'}), 400 + if 'name' not in body: + return jsonify({'msg': 'name field is mandatory'}) + email = body.get("email") + password = body.get("password") + name = body.get("name") + + user = User.query.filter_by(email=email).first() + if user is not None: + return jsonify({'msg': 'Usuario ya registrado!'}), 400 + + pw_hash = bcrypt.generate_password_hash(password).decode('utf-8') + user = User(email=email, name=name, password_hash=pw_hash) + db.session.add(user) + db.session.commit() + + + # access_token = create_access_token(identity=str(user.id)) + return jsonify({'msg': 'User Registered successfully'}) + +@app.route('/api/login', methods=['POST']) +def login(): + + body = request.get_json(silent=True) + # validar body + if body is None: + return jsonify({'msg': 'POST method needs a body or email/password not found.'}), 400 + if 'email' not in body: + return jsonify({'msg': 'email field is mandatory'}), 400 + if 'password' not in body: + return jsonify({'msg': 'password field is mandatory'}), 400 + # buscar usuario + email = body['email'] + password = body['password'] + + user = User.query.filter_by(email=email).first() + if not user: + return jsonify({'msg':'User or password incorrect'}), 400 + # validar password + is_password = bcrypt.check_password_hash(user.password_hash, password) + + if not is_password: + return jsonify({'msg': 'User or password incorrect'}), 400 + # crear token + access_token = create_access_token(identity=str(user.id)) + + return jsonify(access_token=access_token) + + +@app.route("/api/me", methods=["GET"]) +@jwt_required() +def me(): + + + current_user = get_jwt_identity() + user = User.query.get(current_user) + print(user) + return jsonify(user.serialize()), 200 + + + # this only runs if `$ python src/main.py` is executed if __name__ == '__main__': From 2d4d4b91bd67ab83042013af197863abf1b4e9cd Mon Sep 17 00:00:00 2001 From: Juanjo Villarejo Cespedes Date: Thu, 6 Nov 2025 10:23:19 +0000 Subject: [PATCH 09/17] UserProfile --- Pipfile.lock | 16 +- index.html | 2 +- package-lock.json | 492 ++++++++++++++++++++++++++- package.json | 32 +- src/front/components/UserProfile.jsx | 109 ++++++ src/front/routes.jsx | 2 + 6 files changed, 628 insertions(+), 25 deletions(-) create mode 100644 src/front/components/UserProfile.jsx diff --git a/Pipfile.lock b/Pipfile.lock index 6ddd9bb9ac..9fcfdf8cb3 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "ffbfb32d0afa5e4bcaba5c2d08c81381a97abd90f22284d2b76647365df5dc50" + "sha256": "34e9dc18f8e20c8abdc412b8fefb2789ba72dd185f691d08d34dcb62e1ab0965" }, "pipfile-spec": 6, "requires": { @@ -207,6 +207,8 @@ "greenlet": { "hashes": [ "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", + "sha256:015d48959d4add5d6c9f6c5210ee3803a830dce46356e3bc326d6776bde54681", + "sha256:03c5136e7be905045160b1b9fdca93dd6727b180feeafda6818e6496434ed8c5", "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079", "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", @@ -219,18 +221,23 @@ "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d", + "sha256:28a3c6b7cd72a96f61b0e4b2a36f681025b60ae4779cc73c1535eb5f29560b10", + "sha256:2917bdf657f5859fbf3386b12d68ede4cf1f04c90c3a6bc1f013dd68a22e2269", "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", + "sha256:326d234cbf337c9c3def0676412eb7040a35a768efc92504b947b3e9cfc7543d", "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633", + "sha256:52206cd642670b0b320a1fd1cbfd95bca0e043179c1d8a045f2c6109dfe973be", "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31", "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b", "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4", + "sha256:6e343822feb58ac4d0a1211bd9399de2b3a04963ddeec21530fc426cc121f19b", "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c", "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98", @@ -243,6 +250,7 @@ "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5", "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", + "sha256:af41be48a4f60429d5cad9d22175217805098a9ef7c40bfef44f7669fb9d74d8", "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c", "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594", @@ -252,14 +260,18 @@ "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b", "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df", + "sha256:c9c6de1940a7d828635fbd254d69db79e54619f165ee7ce32fda763a9cb6a58c", + "sha256:ca7f6f1f2649b89ce02f6f229d7c19f680a6238af656f61e0115b24857917929", "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb", "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", + "sha256:ee7a6ec486883397d70eec05059353b8e83eca9168b9f3f9a361971e77e0bcd0", "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c", - "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968" + "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968", + "sha256:f47617f698838ba98f4ff4189aef02e7343952df3a615f847bb575c3feb177a7" ], "markers": "python_version >= '3.9'", "version": "==3.2.4" diff --git a/index.html b/index.html index 27a99f796e..3531854c7e 100644 --- a/index.html +++ b/index.html @@ -6,7 +6,7 @@ - Hello Rigo + MeetFit
diff --git a/package-lock.json b/package-lock.json index 44f1ece2dc..b36e02c97e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,9 +9,13 @@ "version": "1.0.1", "license": "ISC", "dependencies": { + "bootstrap": "^5.3.8", + "bootstrap-icons": "^1.13.1", "prop-types": "^15.8.1", "react": "^18.2.0", + "react-bootstrap": "^2.10.10", "react-dom": "^18.2.0", + "react-icons": "^5.5.0", "react-router-dom": "^6.18.0" }, "devDependencies": { @@ -291,6 +295,15 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", @@ -943,6 +956,32 @@ "node": ">= 8" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", + "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, "node_modules/@remix-run/router": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.22.0.tgz", @@ -952,6 +991,69 @@ "node": ">=14.0.0" } }, + "node_modules/@restart/hooks": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz", + "integrity": "sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@popperjs/core": "^2.11.8", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.5.0", + "@types/warning": "^3.0.3", + "dequal": "^2.0.3", + "dom-helpers": "^5.2.0", + "uncontrollable": "^8.0.4", + "warning": "^4.0.3" + }, + "peerDependencies": { + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + } + }, + "node_modules/@restart/ui/node_modules/@restart/hooks": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz", + "integrity": "sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@restart/ui/node_modules/uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.14.0" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1001,14 +1103,12 @@ "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", - "dev": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.3.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", - "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -1026,6 +1126,21 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==", + "license": "MIT" + }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -1258,6 +1373,41 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bootstrap": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", + "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT", + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, + "node_modules/bootstrap-icons": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz", + "integrity": "sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1389,6 +1539,12 @@ ], "license": "CC-BY-4.0" }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1414,7 +1570,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/data-view-buffer": { @@ -1531,6 +1686,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -1543,6 +1707,16 @@ "node": ">=6.0.0" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2625,6 +2799,15 @@ "node": ">= 0.4" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -3447,6 +3630,19 @@ "react-is": "^16.13.1" } }, + "node_modules/prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "license": "MIT", + "dependencies": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + }, + "peerDependencies": { + "react": ">=0.14.0" + } + }, "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -3489,6 +3685,37 @@ "node": ">=0.10.0" } }, + "node_modules/react-bootstrap": { + "version": "2.10.10", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.10.tgz", + "integrity": "sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.7", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.9.4", + "@types/prop-types": "^15.7.12", + "@types/react-transition-group": "^4.4.6", + "classnames": "^2.3.2", + "dom-helpers": "^5.2.1", + "invariant": "^2.2.4", + "prop-types": "^15.8.1", + "prop-types-extra": "^1.1.0", + "react-transition-group": "^4.4.5", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + }, + "peerDependencies": { + "@types/react": ">=16.14.8", + "react": ">=16.14.0", + "react-dom": ">=16.14.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -3503,11 +3730,26 @@ "react": "^18.3.1" } }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -3550,6 +3792,22 @@ "react-dom": ">=16.8" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -4102,6 +4360,12 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4225,6 +4489,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", @@ -4322,6 +4601,15 @@ } } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4646,6 +4934,11 @@ "@babel/helper-plugin-utils": "^7.25.9" } }, + "@babel/runtime": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==" + }, "@babel/template": { "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", @@ -4992,11 +5285,73 @@ "fastq": "^1.6.0" } }, + "@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "peer": true + }, + "@react-aria/ssr": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", + "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", + "requires": { + "@swc/helpers": "^0.5.0" + } + }, "@remix-run/router": { "version": "1.22.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.22.0.tgz", "integrity": "sha512-MBOl8MeOzpK0HQQQshKB7pABXbmyHizdTpqnrIseTbsv0nAepwC2ENZa1aaBExNQcpLoXmWthhak8SABLzvGPw==" }, + "@restart/hooks": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz", + "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==", + "requires": { + "dequal": "^2.0.3" + } + }, + "@restart/ui": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@restart/ui/-/ui-1.9.4.tgz", + "integrity": "sha512-N4C7haUc3vn4LTwVUPlkJN8Ach/+yIMvRuTVIhjilNHqegY60SGLrzud6errOMNJwSnmYFnt1J0H/k8FE3A4KA==", + "requires": { + "@babel/runtime": "^7.26.0", + "@popperjs/core": "^2.11.8", + "@react-aria/ssr": "^3.5.0", + "@restart/hooks": "^0.5.0", + "@types/warning": "^3.0.3", + "dequal": "^2.0.3", + "dom-helpers": "^5.2.0", + "uncontrollable": "^8.0.4", + "warning": "^4.0.3" + }, + "dependencies": { + "@restart/hooks": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.5.1.tgz", + "integrity": "sha512-EMoH04NHS1pbn07iLTjIjgttuqb7qu4+/EyhAx27MHpoENcB2ZdSsLTNxmKD+WEPnZigo62Qc8zjGnNxoSE/5Q==", + "requires": { + "dequal": "^2.0.3" + } + }, + "uncontrollable": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-8.0.4.tgz", + "integrity": "sha512-ulRWYWHvscPFc0QQXvyJjY6LIXU56f0h8pQFvhxiKk5V1fcI8gp9Ht9leVAhrVjzqMw0BgjspBINx9r6oyJUvQ==", + "requires": {} + } + } + }, + "@swc/helpers": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", + "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==", + "requires": { + "tslib": "^2.8.0" + } + }, "@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -5041,14 +5396,12 @@ "@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", - "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", - "dev": true + "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==" }, "@types/react": { "version": "18.3.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", "integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==", - "dev": true, "peer": true, "requires": { "@types/prop-types": "*", @@ -5062,6 +5415,17 @@ "dev": true, "requires": {} }, + "@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "requires": {} + }, + "@types/warning": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz", + "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==" + }, "@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -5218,6 +5582,17 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "bootstrap": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.8.tgz", + "integrity": "sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg==", + "requires": {} + }, + "bootstrap-icons": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz", + "integrity": "sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5292,6 +5667,11 @@ "integrity": "sha512-GwNPlWJin8E+d7Gxq96jxM6w0w+VFeyyXRsjU58emtkYqnbwHqXm5uT2uCmO0RQE9htWknOP4xtBlLmM/gWxvQ==", "dev": true }, + "classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5312,8 +5692,7 @@ "csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "data-view-buffer": { "version": "1.0.2", @@ -5385,6 +5764,11 @@ "object-keys": "^1.1.1" } }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" + }, "doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -5394,6 +5778,15 @@ "esutils": "^2.0.2" } }, + "dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -6153,6 +6546,14 @@ "side-channel": "^1.1.0" } }, + "invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -6675,6 +7076,15 @@ "react-is": "^16.13.1" } }, + "prop-types-extra": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", + "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", + "requires": { + "react-is": "^16.3.2", + "warning": "^4.0.0" + } + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -6696,6 +7106,26 @@ "loose-envify": "^1.1.0" } }, + "react-bootstrap": { + "version": "2.10.10", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.10.tgz", + "integrity": "sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ==", + "requires": { + "@babel/runtime": "^7.24.7", + "@restart/hooks": "^0.4.9", + "@restart/ui": "^1.9.4", + "@types/prop-types": "^15.7.12", + "@types/react-transition-group": "^4.4.6", + "classnames": "^2.3.2", + "dom-helpers": "^5.2.1", + "invariant": "^2.2.4", + "prop-types": "^15.8.1", + "prop-types-extra": "^1.1.0", + "react-transition-group": "^4.4.5", + "uncontrollable": "^7.2.1", + "warning": "^4.0.3" + } + }, "react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -6706,11 +7136,22 @@ "scheduler": "^0.23.2" } }, + "react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "requires": {} + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, "react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -6734,6 +7175,17 @@ "react-router": "6.29.0" } }, + "react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, "reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -7111,6 +7563,11 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -7191,6 +7648,17 @@ "which-boxed-primitive": "^1.1.1" } }, + "uncontrollable": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz", + "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==", + "requires": { + "@babel/runtime": "^7.6.3", + "@types/react": ">=16.9.11", + "invariant": "^2.2.4", + "react-lifecycles-compat": "^3.0.4" + } + }, "update-browserslist-db": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", @@ -7223,6 +7691,14 @@ "rollup": "^3.27.1" } }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 0caab10749..097fa74c3c 100755 --- a/package.json +++ b/package.json @@ -8,10 +8,10 @@ "main": "index.js", "scripts": { "dev": "vite", - "start": "vite", - "build": "vite build", - "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview" + "start": "vite", + "build": "vite build", + "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" }, "author": { "name": "Alejandro Sanchez", @@ -30,13 +30,13 @@ "license": "ISC", "devDependencies": { "@types/react": "^18.2.18", - "@types/react-dom": "^18.2.7", - "@vitejs/plugin-react": "^4.0.4", - "eslint": "^8.46.0", - "eslint-plugin-react": "^7.33.1", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.3", - "vite": "^4.4.8" + "@types/react-dom": "^18.2.7", + "@vitejs/plugin-react": "^4.0.4", + "eslint": "^8.46.0", + "eslint-plugin-react": "^7.33.1", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.3", + "vite": "^4.4.8" }, "babel": { "presets": [ @@ -54,9 +54,13 @@ ] }, "dependencies": { + "bootstrap": "^5.3.8", + "bootstrap-icons": "^1.13.1", "prop-types": "^15.8.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-router-dom": "^6.18.0" + "react": "^18.2.0", + "react-bootstrap": "^2.10.10", + "react-dom": "^18.2.0", + "react-icons": "^5.5.0", + "react-router-dom": "^6.18.0" } } diff --git a/src/front/components/UserProfile.jsx b/src/front/components/UserProfile.jsx new file mode 100644 index 0000000000..f320bf5e44 --- /dev/null +++ b/src/front/components/UserProfile.jsx @@ -0,0 +1,109 @@ +import React from "react"; +import { FaUser, FaBookmark, FaCog, FaArrowLeft, FaPen } from "react-icons/fa"; +import { useNavigate } from "react-router-dom"; + +import "bootstrap/dist/css/bootstrap.min.css"; +import "bootstrap-icons/font/bootstrap-icons.css"; + +export const UserProfile = () => { + const navigate = useNavigate(); + + return ( +
+ {/* Header */} +
+ {/* Botón volver */} + + + {/* Botón editar */} + + + {/* Avatar */} +
+ avatar +
Leonardo
+

leonardo@gmail.com

+
+ + {/* Card de opciones */} +
+
    +
  • alert("Ir a perfil")} + style={{ cursor: "pointer" }} + > + Profile + +
  • +
  • alert("Ir a favoritos")} + style={{ cursor: "pointer" }} + > + Bookmarked + +
  • +
  • alert("Ir a configuración")} + style={{ cursor: "pointer" }} + > + Settings + +
  • +
+
+
+ + {/* Footer tipo app */} +
+ + + + +
+
+ ); +}; diff --git a/src/front/routes.jsx b/src/front/routes.jsx index 0557df6141..832e26b3b6 100644 --- a/src/front/routes.jsx +++ b/src/front/routes.jsx @@ -9,6 +9,7 @@ import { Layout } from "./pages/Layout"; import { Home } from "./pages/Home"; import { Single } from "./pages/Single"; import { Demo } from "./pages/Demo"; +import { UserProfile } from "./components/UserProfile"; export const router = createBrowserRouter( createRoutesFromElements( @@ -25,6 +26,7 @@ export const router = createBrowserRouter( } /> } /> {/* Dynamic route for single items */} } /> + } /> ) ); \ No newline at end of file From c06885f3c16e7fe2d4be32a1750d12b9431ae207 Mon Sep 17 00:00:00 2001 From: Juanjo Villarejo Cespedes Date: Thu, 6 Nov 2025 15:28:20 +0000 Subject: [PATCH 10/17] profile y editProfile --- src/front/components/EditProfile.jsx | 140 +++++++++++++++++++++++++++ src/front/components/ProfileView.jsx | 69 +++++++++++++ src/front/components/UserProfile.jsx | 109 --------------------- src/front/main.jsx | 42 ++++---- src/front/routes.jsx | 31 +++--- 5 files changed, 247 insertions(+), 144 deletions(-) create mode 100644 src/front/components/EditProfile.jsx create mode 100644 src/front/components/ProfileView.jsx delete mode 100644 src/front/components/UserProfile.jsx diff --git a/src/front/components/EditProfile.jsx b/src/front/components/EditProfile.jsx new file mode 100644 index 0000000000..5096ecfb0c --- /dev/null +++ b/src/front/components/EditProfile.jsx @@ -0,0 +1,140 @@ +import React, { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { Form, Button, Container, Card, Spinner } from "react-bootstrap"; + +export const EditProfile = () => { + const [formData, setFormData] = useState({ + name: "", + biography: "", + sports: "", + level: "", + avatar_url: "", + latitude: "", + longitude: "" + }); + const [loading, setLoading] = useState(true); + const navigate = useNavigate(); + + const token = localStorage.getItem("token"); + const userId = localStorage.getItem("user_id"); + + useEffect(() => { + const fetchUser = async () => { + try { + const resp = await fetch(`${process.env.BACKEND_URL}/api/user/${userId}`, { + headers: { Authorization: `Bearer ${token}` }, + }); + if (!resp.ok) throw new Error("Error fetching user"); + const data = await resp.json(); + setFormData(data); + } catch (err) { + console.error(err); + } finally { + setLoading(false); + } + }; + if (userId) fetchUser(); + }, [userId, token]); + + const handleChange = (e) => + setFormData({ ...formData, [e.target.name]: e.target.value }); + + const handleSubmit = async (e) => { + e.preventDefault(); + try { + const resp = await fetch(`${process.env.BACKEND_URL}/api/user/${userId}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify(formData), + }); + if (!resp.ok) throw new Error("Error updating profile"); + navigate("/profile"); + } catch (err) { + console.error(err); + } + }; + + if (loading) + return ( +
+ +
+ ); + + return ( + + +

Edit Profile

+ +
+ + Name + + + + + Biography + + + + + Sports + + + + + Level + + + + + Avatar URL + + + +
+ + +
+
+
+
+ ); +}; diff --git a/src/front/components/ProfileView.jsx b/src/front/components/ProfileView.jsx new file mode 100644 index 0000000000..8b4d622e3a --- /dev/null +++ b/src/front/components/ProfileView.jsx @@ -0,0 +1,69 @@ +import React, { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { Card, Button, Container, Row, Col, Spinner } from "react-bootstrap"; + +export const ProfileView = () => { + const [user, setUser] = useState(null); + const navigate = useNavigate(); + + const token = localStorage.getItem("token"); + const userId = localStorage.getItem("user_id"); // guarda este valor al hacer login + + useEffect(() => { + const fetchUser = async () => { + try { + const resp = await fetch(`${process.env.BACKEND_URL}/api/user/${userId}`, { + headers: { Authorization: `Bearer ${token}` }, + }); + if (!resp.ok) throw new Error("Error fetching user"); + const data = await resp.json(); + setUser(data); + } catch (err) { + console.error(err); + } + }; + if (userId) fetchUser(); + }, [userId, token]); + + if (!user) + return ( +
+ +
+ ); + + return ( + + +
+ avatar +
+

{user.name}

+

{user.email}

+

{user.biography || "No biography yet"}

+ +
+
+

Sports: {user.sports || "Not specified"}

+

Level: {user.level || "Not specified"}

+
+ + +
+
+ ); +}; diff --git a/src/front/components/UserProfile.jsx b/src/front/components/UserProfile.jsx deleted file mode 100644 index f320bf5e44..0000000000 --- a/src/front/components/UserProfile.jsx +++ /dev/null @@ -1,109 +0,0 @@ -import React from "react"; -import { FaUser, FaBookmark, FaCog, FaArrowLeft, FaPen } from "react-icons/fa"; -import { useNavigate } from "react-router-dom"; - -import "bootstrap/dist/css/bootstrap.min.css"; -import "bootstrap-icons/font/bootstrap-icons.css"; - -export const UserProfile = () => { - const navigate = useNavigate(); - - return ( -
- {/* Header */} -
- {/* Botón volver */} - - - {/* Botón editar */} - - - {/* Avatar */} -
- avatar -
Leonardo
-

leonardo@gmail.com

-
- - {/* Card de opciones */} -
-
    -
  • alert("Ir a perfil")} - style={{ cursor: "pointer" }} - > - Profile - -
  • -
  • alert("Ir a favoritos")} - style={{ cursor: "pointer" }} - > - Bookmarked - -
  • -
  • alert("Ir a configuración")} - style={{ cursor: "pointer" }} - > - Settings - -
  • -
-
-
- - {/* Footer tipo app */} -
- - - - -
-
- ); -}; diff --git a/src/front/main.jsx b/src/front/main.jsx index a5a3c781dc..74f4dbc99b 100644 --- a/src/front/main.jsx +++ b/src/front/main.jsx @@ -1,29 +1,29 @@ -import React from 'react' -import ReactDOM from 'react-dom/client' -import './index.css' // Global styles for your application -import { RouterProvider } from "react-router-dom"; // Import RouterProvider to use the router -import { router } from "./routes"; // Import the router configuration -import { StoreProvider } from './hooks/useGlobalReducer'; // Import the StoreProvider for global state management -import { BackendURL } from './components/BackendURL'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import "./index.css"; +import { RouterProvider } from "react-router-dom"; +import { router } from "./routes"; +import { StoreProvider } from "./hooks/useGlobalReducer"; +import { BackendURL } from "./components/BackendURL"; const Main = () => { - - if(! import.meta.env.VITE_BACKEND_URL || import.meta.env.VITE_BACKEND_URL == "") return ( - - - + const backendURL = import.meta.env.VITE_BACKEND_URL; + + if ( ! backendURL || backendURL === "") { + return ( + + + ); + } + return ( - - {/* Provide global state to all components */} - - {/* Set up routing for the application */} - - + + + ); -} +}; -// Render the Main component into the root DOM element. -ReactDOM.createRoot(document.getElementById('root')).render(
) +ReactDOM.createRoot(document.getElementById("root")).render(
); diff --git a/src/front/routes.jsx b/src/front/routes.jsx index 832e26b3b6..9ae9528e73 100644 --- a/src/front/routes.jsx +++ b/src/front/routes.jsx @@ -1,32 +1,35 @@ // Import necessary components and functions from react-router-dom. import { - createBrowserRouter, - createRoutesFromElements, - Route, + createBrowserRouter, + createRoutesFromElements, + Route, } from "react-router-dom"; import { Layout } from "./pages/Layout"; import { Home } from "./pages/Home"; import { Single } from "./pages/Single"; import { Demo } from "./pages/Demo"; -import { UserProfile } from "./components/UserProfile"; +import { ProfileView } from "./components/ProfileView"; +import { EditProfile } from "./components/EditProfile"; export const router = createBrowserRouter( - createRoutesFromElements( + createRoutesFromElements( // CreateRoutesFromElements function allows you to build route elements declaratively. // Create your routes here, if you want to keep the Navbar and Footer in all views, add your new routes inside the containing Route. // Root, on the contrary, create a sister Route, if you have doubts, try it! // Note: keep in mind that errorElement will be the default page when you don't get a route, customize that page to make your project more attractive. // Note: The child paths of the Layout element replace the Outlet component with the elements contained in the "element" attribute of these child paths. - // Root Route: All navigation will start from here. - } errorElement={

Not found!

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

Not found!

} > - {/* Nested Routes: Defines sub-routes within the BaseHome component. */} - } /> - } /> {/* Dynamic route for single items */} - } /> - } /> - - ) + {/* Nested Routes: Defines sub-routes within the BaseHome component. */} + } /> + } /> {/* Dynamic route for single items */} + } /> + {/* Rutas del perfil */} + } /> + } /> + + ) ); \ No newline at end of file From a3d81670cc33c902d998b2f28528713adf1ab4cb Mon Sep 17 00:00:00 2001 From: SashaGiT Date: Fri, 7 Nov 2025 02:21:46 +0000 Subject: [PATCH 11/17] =?UTF-8?q?A=C3=B1adidos=20Login,=20Register,=20Forg?= =?UTF-8?q?ot,=20Layout,=20AuthNotice,=20AuthShell,=20index.css,=20routes.?= =?UTF-8?q?jsx.=20-Sasha?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/front/components/AuthNotice.jsx | 15 ++++ src/front/components/AuthShell.jsx | 14 +++ src/front/components/TextInput.jsx | 43 ++++++++++ src/front/index.css | 128 ++++++++++++++++++++++++++++ src/front/pages/Forgot.jsx | 36 ++++++++ src/front/pages/Layout.jsx | 17 +--- src/front/pages/Login.jsx | 70 +++++++++++++++ src/front/pages/Register.jsx | 85 ++++++++++++++++++ src/front/routes.jsx | 42 ++++----- 9 files changed, 410 insertions(+), 40 deletions(-) create mode 100644 src/front/components/AuthNotice.jsx create mode 100644 src/front/components/AuthShell.jsx create mode 100644 src/front/components/TextInput.jsx create mode 100644 src/front/pages/Forgot.jsx create mode 100644 src/front/pages/Login.jsx create mode 100644 src/front/pages/Register.jsx diff --git a/src/front/components/AuthNotice.jsx b/src/front/components/AuthNotice.jsx new file mode 100644 index 0000000000..426c636e95 --- /dev/null +++ b/src/front/components/AuthNotice.jsx @@ -0,0 +1,15 @@ +// Lo he creado para mostrar un aviso genérico relacionado con la autenticación +export function AuthNotice({ icon = "📩", title = "Mira tu email", text = "te hemos enviado las instrucciones a tu email" }) { + return ( +
+
{icon}
+
+
{title}
+
{text}
+
+
+ ); +} diff --git a/src/front/components/AuthShell.jsx b/src/front/components/AuthShell.jsx new file mode 100644 index 0000000000..2354e011e3 --- /dev/null +++ b/src/front/components/AuthShell.jsx @@ -0,0 +1,14 @@ +// Lo he creado para envolver las pantallas de autenticación con un diseño útil +export default function AuthShell({ children, title, subtitle }) { + return ( +
+
+
+ {title &&
{title}
} + {subtitle &&
{subtitle}
} + {children} +
+
+
+ ); +} diff --git a/src/front/components/TextInput.jsx b/src/front/components/TextInput.jsx new file mode 100644 index 0000000000..c4561012d9 --- /dev/null +++ b/src/front/components/TextInput.jsx @@ -0,0 +1,43 @@ +//Input que permite texto y opcionalmente toggle para mostrar/ocultar (para passwords) + +import { useState } from "react"; + +export default function TextInput({ + label, + type = "text", + name, + value, + onChange, + placeholder, + withToggle = false, + required = true, +}) { + const [show, setShow] = useState(false); + const finalType = withToggle ? (show ? "text" : "password") : type; + + return ( +
+ {label && } + + {withToggle && ( + + )} +
+ ); +} diff --git a/src/front/index.css b/src/front/index.css index e69de29bb2..b13c814e6f 100644 --- a/src/front/index.css +++ b/src/front/index.css @@ -0,0 +1,128 @@ +/* Tema de autenticación oscuro */ +.auth-theme { + + --bg: #0f1b2b; + --card: #1a2740; + --muted: #a3b1c6; + --text: #e8eef7; + --accent: #ff6b4a; + --accent-hover: #ff5832; + --input: #0e1a2a; + --radius: 22px; + + + background: #f7eded; + min-height: 100vh; +} + +/* Layout y tarjeta */ +.auth-theme .auth-shell { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: #f7eded; +} + +.auth-theme .auth-card { + width: 360px; + max-width: 92vw; + background: var(--bg); + border-radius: var(--radius); + padding: 28px 22px; + color: var(--text); + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.25); +} + +.auth-theme .auth-title { + font-size: 22px; + font-weight: 700; + text-align: center; + margin: 6px 0 18px; +} +.auth-theme .auth-sub { + color: var(--muted); + font-size: 13px; + text-align: center; + margin-bottom: 12px; +} + +/* Inputs y labels dentro del scope */ +.auth-theme .form-label { + color: var(--muted); + font-size: 13px; + margin-bottom: 6px; +} +.auth-theme .input-dark.form-control { + background: var(--input); + border: 1px solid rgba(255, 255, 255, 0.06); + color: var(--text); + border-radius: 12px; + height: 44px; +} +.auth-theme .input-dark.form-control::placeholder { + color: #7f8ba1; +} +.auth-theme .input-dark.form-control:focus { + background: var(--input); + color: var(--text); + border-color: rgba(255, 255, 255, 0.15); + box-shadow: 0 0 0 3px rgba(255, 107, 74, 0.15); +} + +/* Botones y links (solo auth) */ +.auth-theme .btn-accent { + background: var(--accent); + border: none; + color: #fff; + height: 46px; + border-radius: 12px; + font-weight: 600; +} +.auth-theme .btn-accent:hover { + background: var(--accent-hover); +} + +.auth-theme .link-muted { + color: var(--muted); + font-size: 13px; +} +.auth-theme .link-accent { + color: var(--accent); + text-decoration: none; + font-weight: 600; +} +.auth-theme .link-accent:hover { + color: var(--accent-hover); +} + +/* Icono ojo y notas */ +.auth-theme .eye-btn { + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + background: transparent; + border: 0; + color: #9ab0cc; +} +.auth-theme .small-note { + font-size: 12px; + color: var(--muted); + margin-top: 6px; +} + +/* Select oscuro */ +.auth-theme .select-dark.form-select { + background: var(--input); + border: 1px solid rgba(255, 255, 255, 0.06); + color: var(--text); + border-radius: 12px; + height: 44px; +} +.auth-theme .select-dark.form-select:focus { + background: var(--input); + color: var(--text); + border-color: rgba(255, 255, 255, 0.15); + box-shadow: 0 0 0 3px rgba(255, 107, 74, 0.15); +} diff --git a/src/front/pages/Forgot.jsx b/src/front/pages/Forgot.jsx new file mode 100644 index 0000000000..633798f03d --- /dev/null +++ b/src/front/pages/Forgot.jsx @@ -0,0 +1,36 @@ +// src/front/src/pages/Forgot.jsx +// Forgot: pide email y muestra confirmación (solo UI). + +import { useState } from "react"; +import AuthShell from "../components/AuthShell"; +import TextInput from "../components/TextInput"; + +export function Forgot() { + const [email, setEmail] = useState(""); + const [sent, setSent] = useState(false); + const [err, setErr] = useState(""); + + const submit = (e) => { + e.preventDefault(); + setErr(""); + if (!email) return setErr("Introduce tu email."); + console.log("Forgot (solo UI):", email); + setSent(true); + }; + + return ( + + {!sent ? ( +
+ setEmail(e.target.value)} placeholder="email@example.com" /> + {err &&
{err}
} + + + ) : ( +
+ Revisa tu correo — te hemos enviado instrucciones de recuperación. +
+ )} +
+ ); +} diff --git a/src/front/pages/Layout.jsx b/src/front/pages/Layout.jsx index 9bfa31325c..d086e61119 100644 --- a/src/front/pages/Layout.jsx +++ b/src/front/pages/Layout.jsx @@ -1,15 +1,2 @@ -import { Outlet } from "react-router-dom/dist" -import ScrollToTop from "../components/ScrollToTop" -import { Navbar } from "../components/Navbar" -import { Footer } from "../components/Footer" - -// Base component that maintains the navbar and footer throughout the page and the scroll to top functionality. -export const Layout = () => { - return ( - - - -