Skip to content

Commit 5b028b9

Browse files
authored
Merge pull request #148 from rambit-systems/push-qplwulqykytp
Incremental UI Push #28
2 parents 99366ac + daae423 commit 5b028b9

File tree

17 files changed

+468
-48
lines changed

17 files changed

+468
-48
lines changed

crates/models/src/org.rs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::fmt;
22

3-
use dvf::{EitherSlug, RecordId};
3+
use dvf::{EitherSlug, RecordId, StrictSlug};
44
use model::{Model, SlugFieldGetter};
55
use serde::{Deserialize, Serialize};
66

@@ -18,12 +18,7 @@ pub struct Org {
1818
impl Org {
1919
/// Generates the value of the unique [`Org`] index `ident`.
2020
pub fn unique_index_ident(&self) -> Vec<EitherSlug> {
21-
match self.org_ident {
22-
OrgIdent::Named(ref entity_name) => {
23-
vec![entity_name.clone().into_inner().into()]
24-
}
25-
OrgIdent::UserOrg(_) => Vec::new(),
26-
}
21+
vec![self.org_ident.index_value().into()]
2722
}
2823
}
2924

@@ -83,6 +78,18 @@ pub enum OrgIdent {
8378
UserOrg(RecordId<User>),
8479
}
8580

81+
impl OrgIdent {
82+
/// Calculates the unique index value for this org ident.
83+
pub fn index_value(&self) -> StrictSlug {
84+
match self {
85+
OrgIdent::Named(entity_name) => {
86+
StrictSlug::new(format!("named-{}", entity_name))
87+
}
88+
OrgIdent::UserOrg(u) => StrictSlug::new(format!("user-{}", u)),
89+
}
90+
}
91+
}
92+
8693
impl Model for Org {
8794
type IndexSelector = !;
8895
type UniqueIndexSelector = OrgUniqueIndexSelector;

crates/prime-domain/src/create.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,19 @@ impl PrimeDomainService {
4646
.await
4747
.map(|s| s.id)
4848
}
49+
50+
/// Creates an [`Org`].
51+
pub async fn create_org(
52+
&self,
53+
name: EntityName,
54+
) -> Result<RecordId<Org>, CreateModelError> {
55+
self
56+
.org_repo
57+
.create_model(Org {
58+
id: RecordId::new(),
59+
org_ident: models::OrgIdent::Named(name),
60+
})
61+
.await
62+
.map(|s| s.id)
63+
}
4964
}

crates/prime-domain/src/fetch_by_name.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use db::{FetchModelByIndexError, kv::LaxSlug};
22
use models::{
3-
Cache, CacheUniqueIndexSelector, Org, Store, StoreUniqueIndexSelector,
3+
Cache, CacheUniqueIndexSelector, Org, OrgIdent, OrgUniqueIndexSelector,
4+
Store, StoreUniqueIndexSelector,
45
dvf::{EntityName, RecordId},
56
};
67

@@ -35,4 +36,18 @@ impl PrimeDomainService {
3536
)
3637
.await
3738
}
39+
40+
/// Fetches an [`Org`] by its [`OrgIdent`].
41+
pub async fn fetch_org_by_ident(
42+
&self,
43+
org_ident: OrgIdent,
44+
) -> Result<Option<Org>, FetchModelByIndexError> {
45+
self
46+
.org_repo
47+
.fetch_model_by_unique_index(
48+
OrgUniqueIndexSelector::Ident,
49+
org_ident.index_value().into(),
50+
)
51+
.await
52+
}
3853
}

crates/prime-domain/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod fetch_by_id;
77
mod fetch_by_name;
88
mod fetch_by_org;
99
mod migrate;
10+
pub mod mutate_user;
1011
pub mod narinfo;
1112
mod search_by_user;
1213
pub mod upload;
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//! User mutation logic.
2+
3+
use db::{FetchModelError, PatchModelError};
4+
use models::{Org, User, dvf::RecordId};
5+
6+
use crate::PrimeDomainService;
7+
8+
/// The error enum for the
9+
/// [`add_org_to_user`](PrimeDomainService::add_org_to_user).
10+
#[derive(thiserror::Error, Debug)]
11+
pub enum AddOrgToUserError {
12+
/// The user does not exist.
13+
#[error("The given user does not exist: {0}")]
14+
UserDoesNotExist(RecordId<User>),
15+
/// The org does not exist
16+
#[error("The given org does not exist: {0}")]
17+
OrgDoesNotExist(RecordId<Org>),
18+
/// The org is a personal org, which does not belong to the user.
19+
#[error(
20+
"The given org is a personal org, to which the user cannot be added: {0}"
21+
)]
22+
PersonalOrg(RecordId<Org>),
23+
/// The action has already been completed.
24+
#[error("This action has already been completed")]
25+
Idempotency,
26+
/// A fetch action failed.
27+
#[error("Internal error: failed to fetch model: {0}")]
28+
InternalFetchError(FetchModelError),
29+
/// A patch action failed.
30+
#[error("Internal error: failed to patch model: {0}")]
31+
InternalPatchError(PatchModelError),
32+
}
33+
34+
impl PrimeDomainService {
35+
/// Adds an [`Org`] to a [`User`]'s org list.
36+
pub async fn add_org_to_user(
37+
&self,
38+
user: RecordId<User>,
39+
org: RecordId<Org>,
40+
) -> Result<(), AddOrgToUserError> {
41+
let user = self
42+
.fetch_user_by_id(user)
43+
.await
44+
.map_err(AddOrgToUserError::InternalFetchError)?
45+
.ok_or(AddOrgToUserError::UserDoesNotExist(user))?;
46+
47+
if user.belongs_to_org(org) {
48+
return Err(AddOrgToUserError::Idempotency);
49+
}
50+
51+
let org = self
52+
.fetch_org_by_id(org)
53+
.await
54+
.map_err(AddOrgToUserError::InternalFetchError)?
55+
.ok_or(AddOrgToUserError::OrgDoesNotExist(org))?;
56+
57+
if matches!(org.org_ident, models::OrgIdent::UserOrg(_)) {
58+
return Err(AddOrgToUserError::PersonalOrg(org.id));
59+
}
60+
61+
let new_user = User {
62+
orgs: user.orgs.iter().copied().chain(Some(org.id)).collect(),
63+
..user
64+
};
65+
66+
self
67+
.user_repo
68+
.patch_model(user.id, new_user)
69+
.await
70+
.map_err(AddOrgToUserError::InternalPatchError)?;
71+
72+
Ok(())
73+
}
74+
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ pub fn ArrowPathHeroIcon() -> impl IntoView {
2020
}
2121
}
2222

23+
#[component]
24+
pub fn BuildingOffice2HeroIcon() -> impl IntoView {
25+
view! {
26+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
27+
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 21h19.5m-18-18v18m10.5-18v18m6-13.5V21M6.75 6.75h.75m-.75 3h.75m-.75 3h.75m3-6h.75m-.75 3h.75m-.75 3h.75M6.75 21v-3.375c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21M3 3h12m-.75 4.5H21m-3.75 3.75h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008v-.008Zm0 3h.008v.008h-.008v-.008Z" />
28+
</svg>
29+
}
30+
}
31+
2332
#[component]
2433
pub fn CheckHeroIcon() -> impl IntoView {
2534
view! {

crates/site-app/src/components/input_field/icon.rs

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
use leptos::{either::EitherOf11, prelude::*};
1+
use leptos::{either::EitherOf12, prelude::*};
22

33
use crate::components::{
44
icons::LockClosedHeroIcon, ArchiveBoxHeroIcon, ArrowPathHeroIcon,
5-
CheckHeroIcon, EnvelopeHeroIcon, EyeHeroIcon, EyeSlashHeroIcon,
6-
GlobeAltHeroIcon, KeyHeroIcon, UserHeroIcon, XMarkHeroIcon,
5+
BuildingOffice2HeroIcon, CheckHeroIcon, EnvelopeHeroIcon, EyeHeroIcon,
6+
EyeSlashHeroIcon, GlobeAltHeroIcon, KeyHeroIcon, UserHeroIcon, XMarkHeroIcon,
77
};
88

99
#[derive(Clone, Copy, PartialEq)]
1010
pub enum InputIcon {
1111
ArchiveBox,
12+
BuildingOffice,
1213
Check,
1314
Envelope,
1415
Eye,
@@ -24,17 +25,20 @@ pub enum InputIcon {
2425
#[component]
2526
pub fn InputIconComponent(icon: InputIcon) -> impl IntoView {
2627
let icon = match icon {
27-
InputIcon::ArchiveBox => EitherOf11::A(view! { <ArchiveBoxHeroIcon />}),
28-
InputIcon::Check => EitherOf11::B(view! { <CheckHeroIcon /> }),
29-
InputIcon::Envelope => EitherOf11::C(view! { <EnvelopeHeroIcon /> }),
30-
InputIcon::Eye => EitherOf11::D(view! { <EyeHeroIcon /> }),
31-
InputIcon::EyeSlash => EitherOf11::E(view! { <EyeSlashHeroIcon /> }),
32-
InputIcon::GlobeAlt => EitherOf11::F(view! { <GlobeAltHeroIcon /> }),
33-
InputIcon::Key => EitherOf11::G(view! { <KeyHeroIcon /> }),
34-
InputIcon::Loading => EitherOf11::H(view! { <ArrowPathHeroIcon /> }),
35-
InputIcon::LockClosed => EitherOf11::I(view! { <LockClosedHeroIcon /> }),
36-
InputIcon::User => EitherOf11::J(view! { <UserHeroIcon /> }),
37-
InputIcon::XMark => EitherOf11::K(view! { <XMarkHeroIcon /> }),
28+
InputIcon::ArchiveBox => EitherOf12::A(view! { <ArchiveBoxHeroIcon />}),
29+
InputIcon::BuildingOffice => {
30+
EitherOf12::B(view! { <BuildingOffice2HeroIcon /> })
31+
}
32+
InputIcon::Check => EitherOf12::C(view! { <CheckHeroIcon /> }),
33+
InputIcon::Envelope => EitherOf12::D(view! { <EnvelopeHeroIcon /> }),
34+
InputIcon::Eye => EitherOf12::E(view! { <EyeHeroIcon /> }),
35+
InputIcon::EyeSlash => EitherOf12::F(view! { <EyeSlashHeroIcon /> }),
36+
InputIcon::GlobeAlt => EitherOf12::G(view! { <GlobeAltHeroIcon /> }),
37+
InputIcon::Key => EitherOf12::H(view! { <KeyHeroIcon /> }),
38+
InputIcon::Loading => EitherOf12::I(view! { <ArrowPathHeroIcon /> }),
39+
InputIcon::LockClosed => EitherOf12::J(view! { <LockClosedHeroIcon /> }),
40+
InputIcon::User => EitherOf12::K(view! { <UserHeroIcon /> }),
41+
InputIcon::XMark => EitherOf12::L(view! { <XMarkHeroIcon /> }),
3842
};
3943

4044
view! {

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ pub(super) fn OrgSelectorPopover(user: AuthUser) -> impl IntoView {
6060
user={user}
6161
{..}
6262
node_ref={popover_ref}
63-
class:hidden=move || !is_open()
63+
class:opacity-0=move || !is_open()
64+
class:pointer-events-none=move || !is_open()
6465
/>
6566
</div>
6667
}
@@ -70,7 +71,7 @@ pub(super) fn OrgSelectorPopover(user: AuthUser) -> impl IntoView {
7071
fn OrgSelector(user: AuthUser) -> impl IntoView {
7172
const POPOVER_CLASS: &str =
7273
"absolute left-0 top-[calc(100%+(var(--spacing)*2))] min-w-56 \
73-
elevation-lv1 transition p-2 flex flex-col gap-1";
74+
elevation-lv1 transition p-2 flex flex-col gap-1 opacity-0";
7475

7576
let org_hooks = Signal::stored(
7677
user

crates/site-app/src/hooks.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
33
mod cache_hook;
44
mod create_cache_hook;
5+
mod create_org_hook;
56
mod entry_hook;
67
mod login_hook;
78
mod org_hook;
89
mod signup_hook;
910

1011
// pub use self::cache_hook::*;
1112
pub use self::{
12-
create_cache_hook::*, entry_hook::*, login_hook::*, org_hook::*,
13-
signup_hook::*,
13+
create_cache_hook::*, create_org_hook::*, entry_hook::*, login_hook::*,
14+
org_hook::*, signup_hook::*,
1415
};

0 commit comments

Comments
 (0)