diff --git a/src/baby_monitor/main.py b/src/baby_monitor/main.py index 8817abc..c0b8df6 100644 --- a/src/baby_monitor/main.py +++ b/src/baby_monitor/main.py @@ -119,6 +119,12 @@ def serve_sleep() -> FileResponse: return FileResponse(static_path / "sleep.html") +@app.get("/settings.html", include_in_schema=False) +def serve_settings() -> FileResponse: + """Serve the settings page.""" + return FileResponse(static_path / "settings.html") + + @app.get("/menu.css", include_in_schema=False) def serve_menu_css() -> FileResponse: """Serve the shared menu CSS.""" diff --git a/src/baby_monitor/models/child_invitation.py b/src/baby_monitor/models/child_invitation.py new file mode 100644 index 0000000..1e86d1c --- /dev/null +++ b/src/baby_monitor/models/child_invitation.py @@ -0,0 +1,41 @@ +"""Child invitation model for sharing child access.""" + +from datetime import datetime +from typing import TYPE_CHECKING +from sqlalchemy import Boolean, DateTime, Integer, String, ForeignKey +from sqlalchemy.orm import Mapped, mapped_column + +if TYPE_CHECKING: + from sqlalchemy.orm import DeclarativeBase + + Base = DeclarativeBase +else: + from baby_monitor.repositories.dependencies.get_database import Base + + +class ChildInvitation(Base): + """Invitation code for sharing child access with other users.""" + + __tablename__ = "child_invitations" + + id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) + code: Mapped[str] = mapped_column(String, unique=True, nullable=False) + child_id: Mapped[int] = mapped_column( + Integer, ForeignKey("children.id"), nullable=False + ) + created_by_user_id: Mapped[int] = mapped_column( + Integer, ForeignKey("users.id"), nullable=False + ) + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), nullable=False + ) + expires_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), nullable=False + ) + is_consumed: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) + consumed_by_user_id: Mapped[int | None] = mapped_column( + Integer, ForeignKey("users.id"), nullable=True + ) + consumed_at: Mapped[datetime | None] = mapped_column( + DateTime(timezone=True), nullable=True + ) diff --git a/src/baby_monitor/repositories/__init__.py b/src/baby_monitor/repositories/__init__.py index b909222..228fa33 100644 --- a/src/baby_monitor/repositories/__init__.py +++ b/src/baby_monitor/repositories/__init__.py @@ -1,13 +1,23 @@ """Repository implementations package.""" from baby_monitor.repositories.dependencies import ( + get_child_repository, get_credentials_repository, + get_diaper_change_repository, + get_feeding_repository, + get_invitation_repository, + get_sleep_repository, get_token_repository, get_user_repository, ) __all__ = [ + "get_child_repository", "get_credentials_repository", + "get_diaper_change_repository", + "get_feeding_repository", + "get_invitation_repository", + "get_sleep_repository", "get_token_repository", "get_user_repository", ] diff --git a/src/baby_monitor/repositories/child/sqlite_child.py b/src/baby_monitor/repositories/child/sqlite_child.py index d9868fa..4c4c3b8 100644 --- a/src/baby_monitor/repositories/child/sqlite_child.py +++ b/src/baby_monitor/repositories/child/sqlite_child.py @@ -1,6 +1,6 @@ """SQLite implementation of child repository.""" -from datetime import datetime +from datetime import datetime, UTC from sqlalchemy.orm import Session from baby_monitor.repositories.interfaces.child_repository_interface import ( @@ -105,6 +105,29 @@ class SQLiteChildRepository(ChildRepositoryInterface): return True return False + def add_parent(self, child_id: int, user_id: int) -> bool: + """Add a parent-child relationship.""" + # Check if relationship already exists + existing = ( + self.db.query(ChildParent) + .filter( + ChildParent.child_id == child_id, + ChildParent.user_id == user_id, + ) + .first() + ) + if existing: + return False + + link = ChildParent( + child_id=child_id, + user_id=user_id, + created_at=datetime.now(UTC), + ) + self.db.add(link) + self.db.commit() + return True + def update( self, child_id: int, diff --git a/src/baby_monitor/repositories/child_invitation/__init__.py b/src/baby_monitor/repositories/child_invitation/__init__.py new file mode 100644 index 0000000..0ef7a5f --- /dev/null +++ b/src/baby_monitor/repositories/child_invitation/__init__.py @@ -0,0 +1,7 @@ +"""Child invitation repository implementations.""" + +from baby_monitor.repositories.child_invitation.sqlite_child_invitation import ( # noqa: E501 + SQLiteChildInvitationRepository, +) + +__all__ = ["SQLiteChildInvitationRepository"] diff --git a/src/baby_monitor/repositories/child_invitation/sqlite_child_invitation.py b/src/baby_monitor/repositories/child_invitation/sqlite_child_invitation.py new file mode 100644 index 0000000..0371999 --- /dev/null +++ b/src/baby_monitor/repositories/child_invitation/sqlite_child_invitation.py @@ -0,0 +1,93 @@ +"""SQLite implementation of child invitation repository.""" + +from datetime import datetime, UTC +from sqlalchemy.orm import Session +from baby_monitor.models.child_invitation import ChildInvitation +from baby_monitor.repositories.interfaces.child_invitation import ( + ChildInvitationRepositoryInterface, +) + + +class SQLiteChildInvitationRepository(ChildInvitationRepositoryInterface): + """SQLite implementation of child invitation repository.""" + + def __init__(self, db: Session): + """Initialize the repository with a database session.""" + self.db = db + + def create_invitation( + self, + code: str, + child_id: int, + created_by_user_id: int, + expires_at: datetime, + ) -> int: + """Create a new child invitation.""" + invitation = ChildInvitation( + code=code, + child_id=child_id, + created_by_user_id=created_by_user_id, + created_at=datetime.now(UTC), + expires_at=expires_at, + is_consumed=False, + ) + self.db.add(invitation) + self.db.commit() + self.db.refresh(invitation) + return invitation.id + + def get_by_code(self, code: str) -> dict | None: + """Get invitation by code.""" + invitation = ( + self.db.query(ChildInvitation).filter(ChildInvitation.code == code).first() + ) + if not invitation: + return None + + return { + "id": invitation.id, + "code": invitation.code, + "child_id": invitation.child_id, + "created_by_user_id": invitation.created_by_user_id, + "created_at": invitation.created_at, + "expires_at": invitation.expires_at, + "is_consumed": invitation.is_consumed, + "consumed_by_user_id": invitation.consumed_by_user_id, + "consumed_at": invitation.consumed_at, + } + + def verify_invitation(self, code: str) -> bool: + """Verify if an invitation code is valid and not expired.""" + invitation = ( + self.db.query(ChildInvitation).filter(ChildInvitation.code == code).first() + ) + + if not invitation: + return False + + if invitation.is_consumed: + return False + + # Compare as naive UTC datetimes (SQLite stores without timezone) + now_utc = datetime.now(UTC).replace(tzinfo=None) + if invitation.expires_at < now_utc: + return False + + return True + + def consume_invitation( + self, code: str, consumed_by_user_id: int, consumed_at: datetime + ) -> bool: + """Mark invitation as consumed.""" + invitation = ( + self.db.query(ChildInvitation).filter(ChildInvitation.code == code).first() + ) + + if not invitation: + return False + + invitation.is_consumed = True + invitation.consumed_by_user_id = consumed_by_user_id + invitation.consumed_at = consumed_at + self.db.commit() + return True diff --git a/src/baby_monitor/repositories/dependencies/__init__.py b/src/baby_monitor/repositories/dependencies/__init__.py index 4d81ee4..047de73 100644 --- a/src/baby_monitor/repositories/dependencies/__init__.py +++ b/src/baby_monitor/repositories/dependencies/__init__.py @@ -1,11 +1,23 @@ """Dependency injection for repositories.""" +from .get_child_invitation_repository import get_child_invitation_repository +from .get_child_repository import get_child_repository from .get_credentials_repository import get_credentials_repository +from .get_diaper_change_repository import get_diaper_change_repository +from .get_feeding_repository import get_feeding_repository +from .get_invitation_repository import get_invitation_repository +from .get_sleep_repository import get_sleep_repository from .get_token_repository import get_token_repository from .get_user_repository import get_user_repository __all__ = [ - "get_user_repository", - "get_token_repository", + "get_child_invitation_repository", + "get_child_repository", "get_credentials_repository", + "get_diaper_change_repository", + "get_feeding_repository", + "get_invitation_repository", + "get_sleep_repository", + "get_token_repository", + "get_user_repository", ] diff --git a/src/baby_monitor/repositories/dependencies/get_child_invitation_repository.py b/src/baby_monitor/repositories/dependencies/get_child_invitation_repository.py new file mode 100644 index 0000000..76ffad7 --- /dev/null +++ b/src/baby_monitor/repositories/dependencies/get_child_invitation_repository.py @@ -0,0 +1,16 @@ +"""Dependency for child invitation repository.""" + +from typing import Annotated +from fastapi import Depends +from sqlalchemy.orm import Session +from baby_monitor.repositories.child_invitation.sqlite_child_invitation import ( # noqa: E501 + SQLiteChildInvitationRepository, +) +from baby_monitor.repositories.dependencies.get_database import get_database + + +def get_child_invitation_repository( + db: Annotated[Session, Depends(get_database)], +) -> SQLiteChildInvitationRepository: + """Get child invitation repository instance.""" + return SQLiteChildInvitationRepository(db) diff --git a/src/baby_monitor/repositories/dependencies/get_database.py b/src/baby_monitor/repositories/dependencies/get_database.py index fbe0d36..229dd0e 100644 --- a/src/baby_monitor/repositories/dependencies/get_database.py +++ b/src/baby_monitor/repositories/dependencies/get_database.py @@ -83,6 +83,7 @@ def init_db() -> None: from baby_monitor.models.db.diaper_change import DiaperChange # noqa: F401 from baby_monitor.models.db.sleep import Sleep # noqa: F401 from baby_monitor.models.invitation import Invitation # noqa: F401 + from baby_monitor.models.child_invitation import ChildInvitation # noqa: F401 # Ensure data directory exists DATA_DIR.mkdir(parents=True, exist_ok=True) diff --git a/src/baby_monitor/repositories/interfaces/__init__.py b/src/baby_monitor/repositories/interfaces/__init__.py index f454ec0..dd09ce8 100644 --- a/src/baby_monitor/repositories/interfaces/__init__.py +++ b/src/baby_monitor/repositories/interfaces/__init__.py @@ -1,5 +1,8 @@ """Repository interfaces package.""" +from .child_invitation import ( + ChildInvitationRepositoryInterface, +) from .credentials_repository_interface import ( CredentialsRepositoryInterface, ) @@ -26,12 +29,13 @@ from .sleep_repository_interface import ( ) __all__ = [ - "UserRepositoryInterface", - "TokenRepositoryInterface", - "CredentialsRepositoryInterface", - "InvitationRepositoryInterface", + "ChildInvitationRepositoryInterface", "ChildRepositoryInterface", - "FeedingRepositoryInterface", + "CredentialsRepositoryInterface", "DiaperChangeRepositoryInterface", + "FeedingRepositoryInterface", + "InvitationRepositoryInterface", "SleepRepositoryInterface", + "TokenRepositoryInterface", + "UserRepositoryInterface", ] diff --git a/src/baby_monitor/repositories/interfaces/child_invitation.py b/src/baby_monitor/repositories/interfaces/child_invitation.py new file mode 100644 index 0000000..34fad1c --- /dev/null +++ b/src/baby_monitor/repositories/interfaces/child_invitation.py @@ -0,0 +1,36 @@ +"""Repository interface for child invitation operations.""" + +from abc import ABC, abstractmethod +from datetime import datetime + + +class ChildInvitationRepositoryInterface(ABC): + """Interface for child invitation repository operations.""" + + @abstractmethod + def create_invitation( + self, + code: str, + child_id: int, + created_by_user_id: int, + expires_at: datetime, + ) -> int: + """Create a new child invitation.""" + pass + + @abstractmethod + def get_by_code(self, code: str) -> dict | None: + """Get invitation by code.""" + pass + + @abstractmethod + def verify_invitation(self, code: str) -> bool: + """Verify if an invitation code is valid and not expired.""" + pass + + @abstractmethod + def consume_invitation( + self, code: str, consumed_by_user_id: int, consumed_at: datetime + ) -> bool: + """Mark invitation as consumed.""" + pass diff --git a/src/baby_monitor/repositories/interfaces/child_repository_interface.py b/src/baby_monitor/repositories/interfaces/child_repository_interface.py index b14a109..4339d0e 100644 --- a/src/baby_monitor/repositories/interfaces/child_repository_interface.py +++ b/src/baby_monitor/repositories/interfaces/child_repository_interface.py @@ -42,3 +42,7 @@ class ChildRepositoryInterface(ABC): @abstractmethod def remove_parent(self, child_id: int, user_id: int) -> bool: """Remove a parent-child relationship.""" + + @abstractmethod + def add_parent(self, child_id: int, user_id: int) -> bool: + """Add a parent-child relationship.""" diff --git a/src/baby_monitor/repositories/interfaces/user_repository_interface.py b/src/baby_monitor/repositories/interfaces/user_repository_interface.py index 50fe515..6d4d452 100644 --- a/src/baby_monitor/repositories/interfaces/user_repository_interface.py +++ b/src/baby_monitor/repositories/interfaces/user_repository_interface.py @@ -25,3 +25,7 @@ class UserRepositoryInterface(ABC): @abstractmethod def delete(self, user_id: int) -> bool: """Delete a user by ID. Returns True if successful.""" + + @abstractmethod + def update_password(self, user_id: int, hashed_password: str) -> bool: + """Update user password. Returns True if successful.""" diff --git a/src/baby_monitor/repositories/user/sqlite_user.py b/src/baby_monitor/repositories/user/sqlite_user.py index 78e98e0..2cf6ac4 100644 --- a/src/baby_monitor/repositories/user/sqlite_user.py +++ b/src/baby_monitor/repositories/user/sqlite_user.py @@ -78,3 +78,12 @@ class SQLiteUserRepository(UserRepositoryInterface): self.db.commit() return True return False + + def update_password(self, user_id: int, hashed_password: str) -> bool: + """Update user password. Returns True if successful.""" + user = self.db.query(User).filter(User.id == user_id).first() + if user: + user.hashed_password = hashed_password # type: ignore + self.db.commit() + return True + return False diff --git a/src/baby_monitor/routers/auth.py b/src/baby_monitor/routers/auth.py index d715332..294d0c5 100644 --- a/src/baby_monitor/routers/auth.py +++ b/src/baby_monitor/routers/auth.py @@ -3,6 +3,7 @@ import secrets from fastapi import APIRouter, HTTPException, Depends, Header from typing import Annotated +from pydantic import BaseModel from baby_monitor.models.auth import ( LoginRequest, @@ -11,22 +12,35 @@ from baby_monitor.models.auth import ( RegisterResponse, ) from baby_monitor.repositories import ( + get_child_repository, + get_diaper_change_repository, + get_feeding_repository, + get_invitation_repository, + get_sleep_repository, get_token_repository, get_user_repository, ) -from baby_monitor.repositories.dependencies.get_invitation_repository import ( - get_invitation_repository, -) from baby_monitor.repositories.interfaces import ( TokenRepositoryInterface, UserRepositoryInterface, InvitationRepositoryInterface, + ChildRepositoryInterface, + FeedingRepositoryInterface, + DiaperChangeRepositoryInterface, + SleepRepositoryInterface, ) from baby_monitor.utils.password import hash_password, verify_password router = APIRouter(prefix="/api", tags=["authentication"]) +class ChangePasswordRequest(BaseModel): + """Request model for password change.""" + + current_password: str + new_password: str + + def verify_token( authorization: Annotated[str | None, Header()] = None, token_repo: TokenRepositoryInterface = Depends(get_token_repository), @@ -191,3 +205,97 @@ def get_current_user( "username": user["username"], "is_admin": user.get("is_admin", False), } + + +@router.post("/change-password") +def change_password( + request: ChangePasswordRequest, + user_id: Annotated[int, Depends(verify_token)], + user_repo: Annotated[UserRepositoryInterface, Depends(get_user_repository)], # noqa: E501 +) -> dict: + """Change password for the current user.""" + # Get user with credentials + user = user_repo.get_by_id(user_id) + if not user: + raise HTTPException(status_code=404, detail="User not found") + + # Verify current password + if not verify_password(request.current_password, user["hashed_password"]): + raise HTTPException(status_code=401, detail="Current password is incorrect") + + # Hash new password + new_password_hash = hash_password(request.new_password) + + # Update password + success = user_repo.update_password(user_id, new_password_hash) + if not success: + raise HTTPException(status_code=500, detail="Failed to update password") + + return {"message": "Password changed successfully"} + + +@router.delete("/account") +def delete_account( + user_id: Annotated[int, Depends(verify_token)], + user_repo: Annotated[UserRepositoryInterface, Depends(get_user_repository)], # noqa: E501 + child_repo: Annotated[ChildRepositoryInterface, Depends(get_child_repository)], # noqa: E501 + feeding_repo: Annotated[ + FeedingRepositoryInterface, Depends(get_feeding_repository) + ], # noqa: E501 + diaper_repo: Annotated[ + DiaperChangeRepositoryInterface, Depends(get_diaper_change_repository) + ], # noqa: E501 + sleep_repo: Annotated[SleepRepositoryInterface, Depends(get_sleep_repository)], # noqa: E501 +) -> dict: + """Delete current user's account and cascade delete orphaned children.""" + # Get user's children + children = child_repo.get_by_user_id(user_id) + + deleted_children = [] + deleted_feedings = 0 + deleted_diapers = 0 + deleted_sleeps = 0 + + # Process each child + for child in children: + child_id = child["id"] + + # Remove the parent-child relationship + child_repo.remove_parent(child_id, user_id) + + # Check if child has any remaining parents + remaining_parents = child_repo.get_parent_ids(child_id) + + if len(remaining_parents) == 0: + # Child is orphaned, delete all logs first + feedings = feeding_repo.get_by_child_id(child_id) + for feeding in feedings: + feeding_repo.delete(feeding["id"]) + deleted_feedings += 1 + + diapers = diaper_repo.get_by_child_id(child_id) + for diaper in diapers: + diaper_repo.delete(diaper["id"]) + deleted_diapers += 1 + + sleeps = sleep_repo.get_by_child_id(child_id) + for sleep in sleeps: + sleep_repo.delete(sleep["id"]) + deleted_sleeps += 1 + + # Now delete the child + child_repo.delete(child_id) + deleted_children.append(child["name"]) + + # Finally, delete the user + user_repo.delete(user_id) + + return { + "message": "Account deleted successfully", + "deleted_children": deleted_children, + "deleted_logs": { + "feedings": deleted_feedings, + "diaper_changes": deleted_diapers, + "sleep_sessions": deleted_sleeps, + }, + } diff --git a/src/baby_monitor/routers/child.py b/src/baby_monitor/routers/child.py index 55fe089..ead85c6 100644 --- a/src/baby_monitor/routers/child.py +++ b/src/baby_monitor/routers/child.py @@ -1,7 +1,10 @@ """Child management router.""" +import secrets +from datetime import datetime, timedelta, UTC from typing import Annotated from fastapi import APIRouter, HTTPException, Depends +from pydantic import BaseModel from baby_monitor.models.child import CreateChildRequest, ChildResponse from baby_monitor.routers.auth import verify_token @@ -9,10 +12,35 @@ from baby_monitor.repositories.child.sqlite_child import SQLiteChildRepository from baby_monitor.repositories.dependencies.get_child_repository import ( get_child_repository, ) +from baby_monitor.repositories.dependencies.get_child_invitation_repository import ( # noqa: E501 + get_child_invitation_repository, +) +from baby_monitor.repositories.interfaces import ChildInvitationRepositoryInterface # noqa: E501 router = APIRouter(prefix="/api/children", tags=["children"]) +class CreateChildInvitationRequest(BaseModel): + """Request model for creating a child invitation.""" + + child_id: int + + +class ChildInvitationResponse(BaseModel): + """Response model for child invitation.""" + + code: str + child_id: int + expires_at: str + message: str + + +class RedeemChildInvitationRequest(BaseModel): + """Request model for redeeming a child invitation.""" + + code: str + + @router.post("", response_model=ChildResponse, status_code=201) def create_child( request: CreateChildRequest, @@ -90,3 +118,91 @@ def update_child( raise HTTPException(status_code=500, detail="Update failed") return ChildResponse(**updated_child) + + +@router.post("/invitations", response_model=ChildInvitationResponse) +def create_child_invitation( + request: CreateChildInvitationRequest, + user_id: Annotated[int, Depends(verify_token)], + child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], # noqa: E501 + invitation_repo: Annotated[ + ChildInvitationRepositoryInterface, Depends(get_child_invitation_repository) + ], # noqa: E501 +) -> ChildInvitationResponse: + """Create an invitation code to share child access with another user.""" + # Verify the child exists and user has access + child = child_repo.get_by_id(request.child_id) + if not child: + raise HTTPException(status_code=404, detail="Child not found") + + parent_ids = child_repo.get_parent_ids(request.child_id) + if user_id not in parent_ids: + raise HTTPException(status_code=403, detail="Access denied") + + # Generate a short invitation code (8 characters) + code = secrets.token_urlsafe(6)[:8].upper() + + # Calculate expiration (7 days from now) + expires_at = datetime.now(UTC) + timedelta(days=7) + + # Create the invitation + invitation_repo.create_invitation( + code=code, + child_id=request.child_id, + created_by_user_id=user_id, + expires_at=expires_at, + ) + + return ChildInvitationResponse( + code=code, + child_id=request.child_id, + expires_at=expires_at.isoformat(), + message="Invitation code created. Valid for 7 days.", + ) + + +@router.post("/invitations/redeem") +def redeem_child_invitation( + request: RedeemChildInvitationRequest, + user_id: Annotated[int, Depends(verify_token)], + child_repo: Annotated[SQLiteChildRepository, Depends(get_child_repository)], # noqa: E501 + invitation_repo: Annotated[ + ChildInvitationRepositoryInterface, Depends(get_child_invitation_repository) + ], # noqa: E501 +) -> dict: + """Redeem an invitation code to gain access to a child.""" + # Verify the invitation code + if not invitation_repo.verify_invitation(request.code): + raise HTTPException( + status_code=400, detail="Invalid or expired invitation code" + ) + + # Get the invitation details + invitation = invitation_repo.get_by_code(request.code) + if not invitation: + raise HTTPException(status_code=400, detail="Invalid invitation code") + + child_id = invitation["child_id"] + + # Check if user already has access to this child + parent_ids = child_repo.get_parent_ids(child_id) + if user_id in parent_ids: + raise HTTPException( + status_code=400, detail="You already have access to this child" + ) + + # Add user as a parent + child_repo.add_parent(child_id, user_id) + + # Consume the invitation + invitation_repo.consume_invitation(request.code, user_id, datetime.now(UTC)) + + # Get child details + child = child_repo.get_by_id(child_id) + if not child: + raise HTTPException(status_code=404, detail="Child not found") + + return { + "message": f"Successfully added access to {child['name']}", + "child": ChildResponse(**child), + } diff --git a/src/baby_monitor/static/index.html b/src/baby_monitor/static/index.html index bcc1437..6b12842 100644 --- a/src/baby_monitor/static/index.html +++ b/src/baby_monitor/static/index.html @@ -23,11 +23,10 @@ color: #333; margin-bottom: 1rem; } - .user-info { - background: #e3f2fd; - padding: 1rem; - border-radius: 4px; - margin-bottom: 1rem; + .greeting { + color: #666; + font-size: 1.1rem; + margin-bottom: 2rem; } button { padding: 0.75rem 1.5rem; @@ -448,10 +447,8 @@ `; document.getElementById("content").innerHTML = ` -
Let's keep track of your baby's care.
diff --git a/src/baby_monitor/static/menu.js b/src/baby_monitor/static/menu.js index afcc947..589f064 100644 --- a/src/baby_monitor/static/menu.js +++ b/src/baby_monitor/static/menu.js @@ -5,6 +5,7 @@ * @param {boolean} options.includeFeedings - Whether to include Feedings menu item * @param {boolean} options.includeDiapers - Whether to include Diapers menu item * @param {boolean} options.includeSleep - Whether to include Sleep menu item + * @param {boolean} options.includeSettings - Whether to include Settings menu item */ function initBurgerMenu(options = {}) { const { @@ -12,6 +13,7 @@ function initBurgerMenu(options = {}) { includeFeedings = true, includeDiapers = true, includeSleep = true, + includeSettings = true, } = options; // Create burger menu HTML @@ -29,7 +31,7 @@ function initBurgerMenu(options = {}) { ${includeDiapers ? '' : ""} ${includeFeedings ? '' : ""} ${includeSleep ? '' : ""} - + ${includeSettings ? '' : ""} `; @@ -65,10 +67,6 @@ function initBurgerMenu(options = {}) { }); } - document.getElementById("menuAddChild").addEventListener("click", () => { - window.location.href = "/add-child.html"; - }); - if (includeFeedings) { document.getElementById("menuFeedings")?.addEventListener("click", () => { window.location.href = "/feedings.html"; @@ -87,6 +85,12 @@ function initBurgerMenu(options = {}) { }); } + if (includeSettings) { + document.getElementById("menuSettings")?.addEventListener("click", () => { + window.location.href = "/settings.html"; + }); + } + document.getElementById("menuLogout").addEventListener("click", () => { closeMenu(); // Call the page's logout function if it exists, otherwise use default diff --git a/src/baby_monitor/static/settings.html b/src/baby_monitor/static/settings.html new file mode 100644 index 0000000..cae5bc2 --- /dev/null +++ b/src/baby_monitor/static/settings.html @@ -0,0 +1,845 @@ + + + + + +Manage your account and children
+ + + ++ Deleting your account is permanent and cannot be undone. This will: +
+Loading children...
+