Skip to content

Commit f62dbce

Browse files
feat(playground): add copy button when creating an API key (#896)
* feat(playground): add copy button when creating an API key * feat(playground): enhance API key modal design, add copy button * feat(playground): add default expiration date to one year * restore area --------- Co-authored-by: leoguillaume <leo.ch.guillaume@gmail.com>
1 parent 8ec6af3 commit f62dbce

2 files changed

Lines changed: 69 additions & 19 deletions

File tree

playground/app/features/keys/components/dialogs.py

Lines changed: 55 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,18 @@
22

33
import reflex as rx
44

5-
from app.core.variables import ICON_SIZE_MEDIUM, ICON_SIZE_XL, MAX_DIALOG_WIDTH, SIZE_MEDIUM, SPACING_LARGE, SPACING_SMALL, TEXT_SIZE_LABEL
5+
from app.core.variables import (
6+
ICON_SIZE_MEDIUM,
7+
ICON_SIZE_XL,
8+
MAX_DIALOG_WIDTH,
9+
PADDING_MEDIUM,
10+
SIZE_MEDIUM,
11+
SPACING_LARGE,
12+
SPACING_MEDIUM,
13+
SPACING_SMALL,
14+
SPACING_TINY,
15+
TEXT_SIZE_LABEL,
16+
)
617
from app.features.keys.state import KeysState
718
from app.shared.components.dialogs import entity_delete_dialog
819

@@ -11,23 +22,32 @@ def keys_created_dialog() -> rx.Component:
1122
"""Dialog to display the newly created API key."""
1223
return rx.dialog.root(
1324
rx.dialog.content(
14-
rx.dialog.title(
25+
rx.vstack(
26+
# Header
1527
rx.hstack(
16-
rx.icon("check_check", size=ICON_SIZE_XL, color=rx.color("green", 11)),
17-
"API Key created successfully!",
18-
spacing=SPACING_SMALL,
28+
rx.icon("circle_check", size=ICON_SIZE_XL, color=rx.color("green", 9)),
29+
rx.vstack(
30+
rx.dialog.title("API key created", margin="0"),
31+
spacing=SPACING_TINY,
32+
align="start",
33+
),
34+
spacing=SPACING_MEDIUM,
1935
align="center",
20-
)
21-
),
22-
rx.dialog.description(
23-
"Copy your API key now. You won't be able to see it again!",
24-
color=rx.color("red", 11),
25-
weight="bold",
26-
),
27-
rx.vstack(
36+
width="100%",
37+
),
38+
# Warning
39+
rx.callout(
40+
"Copy your key now, for security reasons you won't be able to see it again.",
41+
icon="triangle_alert",
42+
color_scheme="amber",
43+
variant="surface",
44+
size="1",
45+
width="100%",
46+
),
47+
# Key
2848
rx.vstack(
2949
rx.text(
30-
"Your API Key:",
50+
"Your API key",
3151
size=TEXT_SIZE_LABEL,
3252
weight="bold",
3353
color=rx.color("mauve", 11),
@@ -42,19 +62,35 @@ def keys_created_dialog() -> rx.Component:
4262
spacing=SPACING_SMALL,
4363
width="100%",
4464
),
45-
rx.dialog.close(
65+
# Footer
66+
rx.hstack(
67+
rx.dialog.close(
68+
rx.button(
69+
"Done",
70+
on_click=KeysState.clear_created_key,
71+
variant="soft",
72+
color_scheme="gray",
73+
size=SIZE_MEDIUM,
74+
),
75+
),
4676
rx.button(
47-
rx.icon("check", size=ICON_SIZE_MEDIUM),
48-
"I've copied the key",
49-
on_click=KeysState.clear_created_key,
77+
rx.icon("copy", size=ICON_SIZE_MEDIUM),
78+
"Copy API key",
79+
on_click=[
80+
rx.set_clipboard(KeysState.created_key),
81+
rx.toast.success("API key copied to clipboard", position="bottom-right"),
82+
],
5083
size=SIZE_MEDIUM,
51-
width="100%",
5284
),
85+
spacing=SPACING_MEDIUM,
86+
justify="end",
87+
width="100%",
5388
),
5489
spacing=SPACING_LARGE,
5590
width="100%",
5691
),
5792
max_width=MAX_DIALOG_WIDTH,
93+
padding=PADDING_MEDIUM,
5894
),
5995
open=KeysState.is_created_dialog_open,
6096
on_open_change=KeysState.handle_created_dialog_change,

playground/app/features/keys/state.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,23 @@ def keys(self) -> list[Key]:
3333
"""Get keys list with correct typing for Reflex."""
3434
return self.entities
3535

36+
def _default_expiry_date(self) -> str:
37+
"""Default expiry date: today + 1 year, capped at the configured maximum if any."""
38+
default = dt.datetime.now() + dt.timedelta(days=365)
39+
if configuration.settings.auth_key_max_expiration_days is not None:
40+
max_date = dt.datetime.now() + dt.timedelta(days=configuration.settings.auth_key_max_expiration_days - 1)
41+
default = min(default, max_date)
42+
return default.strftime("%Y-%m-%d")
43+
3644
@rx.event
3745
async def load_entities(self):
3846
"""Load entities."""
3947
if not self.is_authenticated or not self.api_key:
4048
return
4149

50+
if not self.entity_to_create.expires:
51+
self.entity_to_create.expires = self._default_expiry_date()
52+
4253
self.entities_loading = True
4354
yield
4455

@@ -193,6 +204,9 @@ async def create_entity(self):
193204
self.created_key = data.get("key", "")
194205
self.is_created_dialog_open = True
195206

207+
# Reset the create form, keeping the default 1-year expiry
208+
self.entity_to_create = Key(expires=self._default_expiry_date())
209+
196210
yield rx.toast.success("Key created successfully", position="bottom-right")
197211
async for _ in self.load_entities():
198212
yield

0 commit comments

Comments
 (0)