yew_consent/
component.rs

1use super::ConsentState;
2use gloo_storage::Storage;
3use web_sys::console;
4use yew::prelude::*;
5
6fn load_state<T>(key: &str) -> Option<ConsentState<T>>
7where
8    for<'de> T: serde::Deserialize<'de>,
9{
10    gloo_storage::LocalStorage::get(key).ok()
11}
12
13fn store_state<T>(key: &str, state: Option<ConsentState<T>>)
14where
15    for<'de> T: serde::Serialize,
16{
17    match state {
18        Some(state) => {
19            if let Err(err) = gloo_storage::LocalStorage::set(key, state) {
20                console::error_2(&"Unable to store state".into(), &err.to_string().into());
21            }
22        }
23        None => gloo_storage::LocalStorage::delete(key),
24    }
25}
26
27#[derive(Clone, PartialEq)]
28pub struct ConsentContext<T = ()> {
29    callback: Callback<Option<ConsentState<T>>>,
30}
31
32impl<T> ConsentContext<T>
33where
34    for<'de> T: Clone + PartialEq,
35{
36    pub fn set(&self, state: impl Into<Option<ConsentState<T>>>) {
37        self.callback.emit(state.into());
38    }
39}
40
41#[derive(PartialEq, Properties)]
42pub struct ConsentProperties<T>
43where
44    T: PartialEq,
45{
46    #[prop_or("user.consent".into())]
47    pub consent_key: AttrValue,
48
49    #[prop_or_default]
50    pub children: Children,
51
52    pub ask: Callback<ConsentContext<T>, Html>,
53}
54
55#[function_component(Consent)]
56pub fn consent<T>(props: &ConsentProperties<T>) -> Html
57where
58    for<'de> T: Clone + PartialEq + serde::Deserialize<'de> + serde::Serialize + 'static,
59{
60    let consent = use_state_eq(|| load_state(&props.consent_key));
61
62    let callback = use_callback(
63        (props.consent_key.clone(), consent.clone()),
64        |state: Option<ConsentState<T>>, (consent_key, consent)| {
65            store_state(consent_key, state.clone());
66            consent.set(state);
67        },
68    );
69    let context = ConsentContext { callback };
70
71    html!(
72        <ContextProvider<ConsentContext<T>> context={context.clone()}>
73            {
74                match &*consent {
75                    Some(state) => {
76                        let children: Html = props.children.iter().collect();
77                        match &state {
78                            ConsentState::Yes(_) => html!(
79                                <ContextProvider<ConsentState<T>> context={state.clone()}>
80                                    {children}
81                                </ContextProvider<ConsentState<T>>>
82                            ),
83                            ConsentState::No => children,
84                        }
85                    }
86                    None => props.ask.emit(context)
87                }
88            }
89        </ContextProvider<ConsentContext<T>>>
90    )
91}