Skip to content

Commit 4800b90

Browse files
authored
Merge pull request #153 from rambit-systems/push-rrvvltpzqwtu
Incremental UI Push #33
2 parents 301ca85 + 41e3737 commit 4800b90

File tree

8 files changed

+117
-35
lines changed

8 files changed

+117
-35
lines changed

crates/auth-domain/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,8 @@ impl AuthDomainService {
185185
id: user_id,
186186
personal_org: org.id,
187187
orgs: Vec::new(),
188-
name,
188+
name: name.clone(),
189+
name_abbr: User::abbreviate_name(name),
189190
email,
190191
auth,
191192
active_org_index: 0,

crates/models/src/user.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ pub struct User {
2121
pub orgs: Vec<RecordId<Org>>,
2222
/// The user's name.
2323
pub name: HumanName,
24+
/// An abbreviated form of the user's name.
25+
pub name_abbr: HumanName,
2426
/// The user's email address.
2527
pub email: EmailAddress,
2628
/// The user's authentication secrets.
@@ -51,6 +53,19 @@ impl User {
5153
pub fn belongs_to_org(&self, org: RecordId<Org>) -> bool {
5254
self.personal_org == org || self.orgs.contains(&org)
5355
}
56+
57+
/// Helper fn that abbreviates a name.
58+
pub fn abbreviate_name(name: HumanName) -> HumanName {
59+
HumanName::try_new(
60+
name
61+
.to_string()
62+
.split_whitespace()
63+
.filter_map(|word| word.chars().next())
64+
.map(|c| c.to_uppercase().to_string())
65+
.collect::<String>(),
66+
)
67+
.expect("failed to create name")
68+
}
5469
}
5570

5671
/// The unique index selector for [`User`]
@@ -139,6 +154,8 @@ pub struct AuthUser {
139154
pub orgs: Vec<RecordId<Org>>,
140155
/// The user's name.
141156
pub name: HumanName,
157+
/// An abbreviated form of the user's name.
158+
pub name_abbr: HumanName,
142159
/// The hash of the user's authentication secrets.
143160
pub auth_hash_bytes: Box<[u8]>,
144161
/// The index of the [`Org`] that the user is currently operating as.
@@ -154,6 +171,7 @@ impl From<User> for AuthUser {
154171
personal_org: user.personal_org,
155172
orgs: user.orgs,
156173
name: user.name,
174+
name_abbr: user.name_abbr,
157175
auth_hash_bytes,
158176
active_org_index: user.active_org_index,
159177
}

crates/prime-domain/src/migrate.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ impl PrimeDomainService {
4848
.unwrap(),
4949
name: HumanName::try_new("Jean-Luc Picard")
5050
.expect("failed to create name"),
51+
name_abbr: User::abbreviate_name(
52+
HumanName::try_new("Jean-Luc Picard").expect("failed to create name"),
53+
),
5154
auth: models::UserAuthCredentials::Password {
5255
// hash for password `password`
5356
password_hash: models::PasswordHash(

crates/site-app/src/components/navbar.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
mod account_menu;
12
mod org_selector;
23

34
use leptos::{either::Either, prelude::*};
45
use models::AuthUser;
56

6-
use self::org_selector::OrgSelectorPopover;
7+
use self::{account_menu::AccountMenu, org_selector::OrgSelector};
78
use crate::{hooks::OrgHook, navigation::next_url_encoded_hook};
89

910
#[component]
@@ -39,7 +40,7 @@ fn NavbarUserArea() -> impl IntoView {
3940
let auth_user = use_context::<AuthUser>();
4041

4142
match auth_user {
42-
Some(user) => Either::Left(view! { <LoggedInUserAuthActions user=user /> }),
43+
Some(_) => Either::Left(view! { <LoggedInUserAuthActions /> }),
4344
None => Either::Right(view! { <LoggedOutUserAuthActions /> }),
4445
}
4546
}
@@ -61,13 +62,14 @@ fn LoggedOutUserAuthActions() -> impl IntoView {
6162
}
6263

6364
#[component]
64-
fn LoggedInUserAuthActions(user: AuthUser) -> impl IntoView {
65+
fn LoggedInUserAuthActions() -> impl IntoView {
6566
let active_org_hook = OrgHook::new_active();
6667
let active_org_dashboard_url = active_org_hook.dashboard_url();
6768

6869
view! {
69-
<OrgSelectorPopover user=user />
7070
<a href=active_org_dashboard_url class="btn-link btn-link-primary">"Dashboard"</a>
71-
<a href="/auth/logout" class="btn-link btn-link-secondary">"Log Out"</a>
71+
<OrgSelector />
72+
<AccountMenu />
73+
// <a href="/auth/logout" class="btn-link btn-link-secondary">"Log Out"</a>
7274
}
7375
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
use leptos::prelude::*;
2+
use models::AuthUser;
3+
4+
use crate::components::{Popover, PopoverContents, PopoverTrigger};
5+
6+
#[component]
7+
fn AccountMenuTrigger() -> impl IntoView {
8+
let user = expect_context::<AuthUser>();
9+
10+
const CLASS: &str = "size-10 flex flex-col justify-center items-center \
11+
btn-secondary transition-colors rounded-full \
12+
border-[1.5px] border-base-6 cursor-pointer";
13+
14+
view! {
15+
<div class=CLASS>
16+
{ user.name_abbr.to_string() }
17+
</div>
18+
}
19+
}
20+
21+
#[island]
22+
pub(crate) fn AccountMenu() -> impl IntoView {
23+
view! {
24+
<Popover>
25+
<PopoverTrigger slot>
26+
<AccountMenuTrigger />
27+
</PopoverTrigger>
28+
<PopoverContents slot>
29+
<AccountMenuMenu />
30+
</PopoverContents>
31+
</Popover>
32+
}
33+
}
34+
35+
#[component]
36+
fn AccountMenuMenu() -> impl IntoView {
37+
const POPOVER_CLASS: &str =
38+
"absolute right-0 top-[calc(100%+(var(--spacing)*4))] min-w-56 \
39+
elevation-lv1 p-2 flex flex-col gap-1 leading-none";
40+
41+
view! {
42+
<div class=POPOVER_CLASS>
43+
<a class="btn-link btn-link-secondary">"Account Settings"</a>
44+
<a href="/auth/logout" class="btn btn-critical-subtle">"Log Out"</a>
45+
</div>
46+
}
47+
}

crates/site-app/src/components/navbar/org_selector.rs

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,52 +10,60 @@ use crate::{
1010
navigation::navigate_to,
1111
};
1212

13-
#[island]
14-
pub(super) fn OrgSelectorPopover(user: AuthUser) -> impl IntoView {
15-
const CONTAINER_CLASS: &str = "transition hover:bg-base-3 active:bg-base-4 \
16-
cursor-pointer px-2 py-1 rounded flex \
17-
flex-col gap leading-none items-end gap-0";
18-
13+
#[component]
14+
fn OrgSelectorTrigger() -> impl IntoView {
1915
let active_org_hook = OrgHook::new_active();
2016
let active_org_descriptor = active_org_hook.descriptor();
21-
let user_name = Signal::stored(user.name.to_string());
2217

18+
const CLASS: &str = "transition hover:bg-base-3 active:bg-base-4 \
19+
cursor-pointer px-2 py-1 rounded flex flex-col gap-0.5 \
20+
text-sm leading-none items-end gap-0";
21+
22+
view! {
23+
<div class=CLASS>
24+
<p class="text-base/[1] text-base-12">
25+
<Suspense fallback=|| "[loading]">
26+
{ move || Suspend::new(active_org_descriptor) }
27+
</Suspense>
28+
</p>
29+
<div class="flex flex-row items-end gap-0.5">
30+
<p>"Switch Orgs"</p>
31+
<ChevronDownHeroIcon {..} class="size-3 stroke-[3.0] stroke-base-11" />
32+
</div>
33+
</div>
34+
}
35+
}
36+
37+
#[island]
38+
pub(super) fn OrgSelector() -> impl IntoView {
2339
view! {
2440
<Popover>
2541
<PopoverTrigger slot>
26-
<div class=CONTAINER_CLASS>
27-
<span class="text-base-12 text-sm">{ user_name }</span>
28-
<div class="flex flex-row items-center gap-0">
29-
<span class="text-sm">
30-
<Suspense fallback=|| "[loading]">
31-
{ move || Suspend::new(active_org_descriptor) }
32-
</Suspense>
33-
</span>
34-
<ChevronDownHeroIcon {..} class="size-3 stroke-[3.0] stroke-base-11" />
35-
</div>
36-
</div>
42+
<OrgSelectorTrigger />
3743
</PopoverTrigger>
3844

3945
<PopoverContents slot>
40-
<OrgSelector user=user />
46+
<OrgSelectorMenu />
4147
</PopoverContents>
4248
</Popover>
4349
}
4450
}
4551

4652
#[component]
47-
fn OrgSelector(user: AuthUser) -> impl IntoView {
53+
fn OrgSelectorMenu() -> impl IntoView {
54+
let auth_user = expect_context::<AuthUser>();
55+
4856
const POPOVER_CLASS: &str =
49-
"absolute left-0 top-[calc(100%+(var(--spacing)*2))] min-w-56 \
57+
"absolute right-0 top-[calc(100%+(var(--spacing)*4))] min-w-56 \
5058
elevation-lv1 p-2 flex flex-col gap-1 leading-none";
5159

5260
let org_hooks = Signal::stored(
53-
user
61+
auth_user
5462
.iter_orgs()
5563
.map(|o| (o, OrgHook::new(move || o)))
5664
.collect::<Vec<_>>(),
5765
);
58-
let active_org = user.active_org();
66+
let active_org = auth_user.active_org();
5967

6068
let action = ServerAction::<SwitchActiveOrg>::new();
6169
let selected = RwSignal::new(None::<RecordId<Org>>);
@@ -97,9 +105,9 @@ fn OrgSelector(user: AuthUser) -> impl IntoView {
97105

98106
view! {
99107
<div
100-
class="rounded p-2 flex flex-row gap-2 items-center"
101-
class=("text-base-12 font-semibold", id == active_org)
102-
class=("cursor-pointer hover:bg-base-3 active:bg-base-4", id != active_org)
108+
class="rounded p-2 flex flex-row gap-2 items-center transition-colors text-base-12"
109+
class=("font-bold", id == active_org)
110+
class=("cursor-pointer btn-link-secondary", id != active_org)
103111
on:click=handler
104112
>
105113
{ icon_element }
@@ -128,7 +136,8 @@ fn OrgSelector(user: AuthUser) -> impl IntoView {
128136
#[component]
129137
fn CreateOrgRow() -> impl IntoView {
130138
const CLASS: &str = "rounded p-2 flex flex-row gap-2 items-center \
131-
cursor-pointer hover:bg-base-3 active-bg-base-4";
139+
cursor-pointer btn-link-secondary transition-colors \
140+
text-base-12";
132141

133142
view! {
134143
<a href="/org/create_org" class=CLASS>

crates/site-app/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ fn LeptosFetchDevtools() -> impl IntoView {
101101
#[component]
102102
fn PageContainer(children: Children) -> impl IntoView {
103103
view! {
104-
<main class="elevation-suppressed text-base-11 font-normal text-base/[1.2]">
104+
<main class="elevation-suppressed text-base-11 font-medium text-base/[1.2]">
105105
<div class="page-container flex flex-col min-h-svh pb-8">
106106
<self::components::Navbar />
107107
{ children() }

crates/site-app/style/src/main.css

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,17 @@
5050
--form-grid-cols: calc(48 * var(--spacing)) minmax(0, 1fr)
5151
}
5252

53+
/* reduce `rem` on small screen sizes */
5354
:root {
5455
font-size: 85%;
5556
}
56-
5757
@media (min-width: 40rem) {
5858
:root {
5959
font-size: inherit;
6060
}
6161
}
6262

63+
/* custom grid template columns */
6364
@utility grid-cols-form {
6465
grid-template-columns: var(--form-grid-cols);
6566
}
@@ -85,6 +86,7 @@
8586
}
8687
}
8788

89+
/* disregard leptos internal components */
8890
@layer base {
8991
leptos-island {
9092
display: contents;

0 commit comments

Comments
 (0)