diff --git a/playground/app/features/keys/components/dialogs.py b/playground/app/features/keys/components/dialogs.py index 5dd02595c..82df8c793 100644 --- a/playground/app/features/keys/components/dialogs.py +++ b/playground/app/features/keys/components/dialogs.py @@ -2,7 +2,18 @@ import reflex as rx -from app.core.variables import ICON_SIZE_MEDIUM, ICON_SIZE_XL, MAX_DIALOG_WIDTH, SIZE_MEDIUM, SPACING_LARGE, SPACING_SMALL, TEXT_SIZE_LABEL +from app.core.variables import ( + ICON_SIZE_MEDIUM, + ICON_SIZE_XL, + MAX_DIALOG_WIDTH, + PADDING_MEDIUM, + SIZE_MEDIUM, + SPACING_LARGE, + SPACING_MEDIUM, + SPACING_SMALL, + SPACING_TINY, + TEXT_SIZE_LABEL, +) from app.features.keys.state import KeysState from app.shared.components.dialogs import entity_delete_dialog @@ -11,23 +22,32 @@ def keys_created_dialog() -> rx.Component: """Dialog to display the newly created API key.""" return rx.dialog.root( rx.dialog.content( - rx.dialog.title( + rx.vstack( + # Header rx.hstack( - rx.icon("check_check", size=ICON_SIZE_XL, color=rx.color("green", 11)), - "API Key created successfully!", - spacing=SPACING_SMALL, + rx.icon("circle_check", size=ICON_SIZE_XL, color=rx.color("green", 9)), + rx.vstack( + rx.dialog.title("API key created", margin="0"), + spacing=SPACING_TINY, + align="start", + ), + spacing=SPACING_MEDIUM, align="center", - ) - ), - rx.dialog.description( - "Copy your API key now. You won't be able to see it again!", - color=rx.color("red", 11), - weight="bold", - ), - rx.vstack( + width="100%", + ), + # Warning + rx.callout( + "Copy your key now, for security reasons you won't be able to see it again.", + icon="triangle_alert", + color_scheme="amber", + variant="surface", + size="1", + width="100%", + ), + # Key rx.vstack( rx.text( - "Your API Key:", + "Your API key", size=TEXT_SIZE_LABEL, weight="bold", color=rx.color("mauve", 11), @@ -42,19 +62,35 @@ def keys_created_dialog() -> rx.Component: spacing=SPACING_SMALL, width="100%", ), - rx.dialog.close( + # Footer + rx.hstack( + rx.dialog.close( + rx.button( + "Done", + on_click=KeysState.clear_created_key, + variant="soft", + color_scheme="gray", + size=SIZE_MEDIUM, + ), + ), rx.button( - rx.icon("check", size=ICON_SIZE_MEDIUM), - "I've copied the key", - on_click=KeysState.clear_created_key, + rx.icon("copy", size=ICON_SIZE_MEDIUM), + "Copy API key", + on_click=[ + rx.set_clipboard(KeysState.created_key), + rx.toast.success("API key copied to clipboard", position="bottom-right"), + ], size=SIZE_MEDIUM, - width="100%", ), + spacing=SPACING_MEDIUM, + justify="end", + width="100%", ), spacing=SPACING_LARGE, width="100%", ), max_width=MAX_DIALOG_WIDTH, + padding=PADDING_MEDIUM, ), open=KeysState.is_created_dialog_open, on_open_change=KeysState.handle_created_dialog_change, diff --git a/playground/app/features/keys/state.py b/playground/app/features/keys/state.py index 7c7128512..fd607beb7 100644 --- a/playground/app/features/keys/state.py +++ b/playground/app/features/keys/state.py @@ -33,12 +33,23 @@ def keys(self) -> list[Key]: """Get keys list with correct typing for Reflex.""" return self.entities + def _default_expiry_date(self) -> str: + """Default expiry date: today + 1 year, capped at the configured maximum if any.""" + default = dt.datetime.now() + dt.timedelta(days=365) + if configuration.settings.auth_key_max_expiration_days is not None: + max_date = dt.datetime.now() + dt.timedelta(days=configuration.settings.auth_key_max_expiration_days - 1) + default = min(default, max_date) + return default.strftime("%Y-%m-%d") + @rx.event async def load_entities(self): """Load entities.""" if not self.is_authenticated or not self.api_key: return + if not self.entity_to_create.expires: + self.entity_to_create.expires = self._default_expiry_date() + self.entities_loading = True yield @@ -193,6 +204,9 @@ async def create_entity(self): self.created_key = data.get("key", "") self.is_created_dialog_open = True + # Reset the create form, keeping the default 1-year expiry + self.entity_to_create = Key(expires=self._default_expiry_date()) + yield rx.toast.success("Key created successfully", position="bottom-right") async for _ in self.load_entities(): yield