from __future__ import annotations from copy import copy from typing import Any, Optional from uuid import uuid4 from rio import Component, Column, Row, Text, Spacer, page, Color, Rectangle, TextInput, GuardEvent from elm.types import UserSession, User from elm.services import UserService, LocalData, LocalDataService, ConfigurationService from elm.components import ElmButton def login_page_guard(event: GuardEvent) -> Optional[str]: try: _ = event.session[UserSession].user_name return "/" except KeyError: return None @page(name="Login", url_segment="login", guard=login_page_guard) class LoginPage(Component): user_name: str = "" password: str = "" error_on_last_attempt: bool = False login_in_progress: bool = False async def on_login_confirmed(self, _: Any) -> None: """ Handler for pressing ENTER inside the text inputs """ await self.on_login_pressed() async def on_login_pressed(self) -> None: self.login_in_progress = True user_name = copy(self.user_name) # Prevents race condition name swap is_valid = await self.session[UserService].is_login_valid(user_name, self.password) if not is_valid: # Migrated users user_name = user_name.lower().capitalize() is_valid = await self.session[UserService].is_login_valid(user_name, self.password) if is_valid: user: User = await self.session[UserService].get_user(user_name) self.error_on_last_attempt = False user_session = UserSession(id=uuid4(), user_name=user.user_name, is_team_member=user.is_team_member) self.session.attach(user_session) token = self.session[LocalDataService].set_session(user_session) self.session[LocalData].stored_session_token = token self.session[UserSession].profile_picture = await self.load_user_picture() self.session.attach(self.session[LocalData]) self.login_in_progress = False self.session.navigate_to("./") else: self.login_in_progress = False self.error_on_last_attempt = True async def load_user_picture(self) -> bytes: try: user_picture = await self.session[UserService].get_user_picture(self.session[UserSession].user_name) if user_picture is not None and len(user_picture) > 0: return user_picture except KeyError: return self.session[ConfigurationService].DEFAULT_PROFILE_PICTURE return self.session[ConfigurationService].DEFAULT_PROFILE_PICTURE def on_register_pressed(self) -> None: self.session.navigate_to("./register") def on_lost_password_pressed(self) -> None: self.session.navigate_to("./lost-pw") def build(self) -> Component: return Row( Rectangle( content=Column( Rectangle( content=Rectangle( content=Text("Login", margin=0.5, selectable=False, overflow="wrap"), fill=self.session.theme.header_box_background_color, margin=0.4 ), stroke_width=0.1, stroke_color=self.session.theme.box_border_color, ), Column( TextInput( text=self.bind().user_name, label="Nutzername", on_confirm=self.on_login_confirmed ), TextInput( text=self.bind().password, label="Passwort", is_secret=True, on_confirm=self.on_login_confirmed ), Text("Falscher Nutzername oder Passwort", fill=self.session.theme.danger_color, overflow="wrap", justify="center") if self.error_on_last_attempt else Spacer(grow_x=False, grow_y=False), ElmButton(text="Login", style="small" if self.session.is_mobile() else "normal", on_press=self.on_login_pressed, is_loading=self.login_in_progress), ElmButton(text="Passwort\nvergessen" if self.session.is_mobile() else "Passwort vergessen", style="small" if self.session.is_mobile() else "normal", on_press=self.on_lost_password_pressed), ElmButton(text="Account anlegen", style="small" if self.session.is_mobile() else "normal", on_press=self.on_register_pressed), margin=1, spacing=1 ), Spacer() ), fill=self.session.theme.box_color, stroke_width=0.1, stroke_color=self.session.theme.box_border_color, min_height=15 ), align_x=0.5, align_y=0.5 )