chore: make decling and accepting TOS better

This commit is contained in:
LynithDev 2026-09-05 23:12:05 +02:00
parent bd8dc0424c
commit 37001e7a7c
2 changed files with 85 additions and 54 deletions

View file

@ -8,7 +8,7 @@ use oneclient_db::models::ClusterId;
pub struct ActiveClusterState(pub State<Option<ClusterId>>);
pub fn use_provide_active_cluster(active: ActiveClusterState) {
use_provide_root_context(move || active.clone());
use_hook(move || provide_root_context(active));
}
pub fn use_active_cluster_id() -> State<Option<ClusterId>> {
@ -48,7 +48,7 @@ pub fn use_start_maximized() -> bool {
pub struct BrowserCompatState(pub State<bool>);
pub fn use_provide_browser_compat(state: BrowserCompatState) {
use_provide_root_context(move || state.clone());
use_hook(move || provide_root_context(state));
}
pub fn use_browser_compat() -> State<bool> {
@ -89,7 +89,7 @@ impl Default for BrowserUiState {
pub struct BrowserStateStore(pub State<HashMap<String, BrowserUiState>>);
pub fn use_provide_browser_state(store: BrowserStateStore) {
use_provide_root_context(move || store.clone());
use_hook(move || provide_root_context(store));
}
pub fn use_browser_state_store() -> State<HashMap<String, BrowserUiState>> {
@ -114,9 +114,63 @@ pub struct OnboardingSelectionState {
}
pub fn use_provide_onboarding_selection(state: OnboardingSelectionState) {
use_provide_root_context(move || state.clone());
use_hook(move || provide_root_context(state));
}
pub fn use_onboarding_selection() -> OnboardingSelectionState {
consume_root_context::<OnboardingSelectionState>()
}
#[cfg(test)]
mod tests {
use freya_testing::TestingRunner;
use super::*;
/// Stands in for a route layout: it owns the state and publishes it as a root
/// context, so unmounting it drops the value the context still points at
#[derive(PartialEq)]
struct Shell;
impl Component for Shell {
fn render(&self) -> impl IntoElement {
let active = use_state(|| None::<ClusterId>);
use_provide_active_cluster(ActiveClusterState(active));
rect().child(Reader)
}
}
#[derive(PartialEq)]
struct Reader;
impl Component for Reader {
fn render(&self) -> impl IntoElement {
let active = *use_active_cluster_id().read();
label().text(format!("{active:?}"))
}
}
fn app() -> impl IntoElement {
let mounted = use_consume::<State<bool>>();
rect().maybe_child(mounted().then(|| Shell.into_element()))
}
#[test]
fn a_remounted_shell_replaces_the_dropped_root_state() {
let (mut test, mut mounted) = TestingRunner::new(
app,
Size2D::new(300., 300.),
|runner| runner.provide_root_context(|| State::create(true)),
1.,
);
test.sync_and_update();
// Leaving the shell drops the state the root context holds
test.run_in(|| mounted.set(false));
test.sync_and_update();
// Coming back must not hand the reader that dropped state
test.run_in(|| mounted.set(true));
test.sync_and_update();
}
}

View file

@ -4,7 +4,7 @@ use freya::router::RouterContext;
use crate::components::{
Button, Icon, IconType, Markdown, MarkdownStyle, OverlayPopup, ScrollArea, Segment,
SegmentedControl, toggle,
SegmentedControl,
};
use crate::hooks::{
TermsQuery, has_migration_data, terms_document, terms_error, terms_is_loading, use_dispatch,
@ -38,7 +38,6 @@ impl Component for OnboardingTerms {
let error = terms_error(&query);
let loading = terms_is_loading(&query);
let accepted = use_state(|| false);
let tab = use_state(|| LegalTab::Terms);
let confirming_decline = use_state(|| false);
@ -100,19 +99,21 @@ impl Component for OnboardingTerms {
.maybe_child(tabs)
.child(body)
.child(link_row(terms_url, privacy_url))
.child(accept_row(accepted))
.into_element();
let accept_dispatch = dispatch.clone();
let nav = terms_nav(back, !loading, move || {
if *accepted.read() {
let nav = terms_nav(
back,
!loading,
move || {
accept_dispatch.accept_tos(terms_version, privacy_version);
let _ = RouterContext::get().replace(next.clone());
} else {
},
move || {
let mut confirming = confirming_decline;
confirming.set(true);
}
});
},
);
let modal = confirming_decline.read().then(|| {
decline_modal(confirming_decline, move || {
@ -266,46 +267,14 @@ fn external_link_button(text: &'static str, url: String) -> impl IntoElement {
.child(Icon::new(IconType::LinkExternal01).size(14.))
}
fn accept_row(accepted: State<bool>) -> Element {
rect()
.horizontal()
.width(Size::fill())
.cross_align(Alignment::Center)
.spacing(16.)
.content(Content::Flex)
.padding(Gaps::new_symmetric(12., 16.))
.corner_radius(CornerRadius::new_all(12.))
.background(colors::page_elevated())
.border(border_all_color(1., colors::component_border()))
.child(
rect()
.vertical()
.width(Size::flex(1.0))
.spacing(3.)
.child(
label()
.text("I accept the Terms of Service and Privacy Policy")
.font_size(14.)
.font_weight(FontWeight::MEDIUM)
.color(colors::fg_primary()),
)
.child(
label()
.text("Required for world hosting, socials, nametag indicator, and more.")
.font_size(11.)
.color(colors::fg_secondary()),
),
)
.child(toggle(accepted))
.into_element()
}
fn terms_nav(
back: Option<Route>,
next_enabled: bool,
on_next: impl FnMut() + 'static,
accept_enabled: bool,
on_accept: impl FnMut() + 'static,
on_decline: impl FnMut() + 'static,
) -> Element {
let mut on_next = on_next;
let mut on_accept = on_accept;
let mut on_decline = on_decline;
rect()
.horizontal()
.width(Size::fill())
@ -323,14 +292,22 @@ fn terms_nav(
.text("Back")
.into_element()
}))
.child(
Button::new()
.secondary()
.width(Size::px(140.))
.on_press(move |_| on_decline())
.text("Decline")
.child(Icon::new(IconType::X).size(16.)),
)
.child(
Button::new()
.primary()
.width(Size::px(140.))
.enabled(next_enabled)
.on_press(move |_| on_next())
.text("Next")
.child(Icon::new(IconType::ArrowRight).size(16.)),
.enabled(accept_enabled)
.on_press(move |_| on_accept())
.text("Accept")
.child(Icon::new(IconType::Check).size(16.)),
)
.into_element()
}
@ -407,7 +384,7 @@ fn decline_modal(confirming: State<bool>, on_confirm: impl FnMut() + 'static) ->
Button::new()
.secondary()
.on_press(move |_| confirming.set(false))
.text("Close"),
.text("Cancel"),
)
.child(
Button::new()