webapp_frontend/component/
root.rs

1//! The Root component as main entry point of the frontend application
2
3use crate::{
4    api::Response,
5    component::{content::ContentComponent, login::LoginComponent},
6    route::RouterTarget,
7    service::{
8        cookie::CookieService,
9        uikit::{NotificationStatus, UIkitService},
10    },
11    string::{REQUEST_ERROR, RESPONSE_ERROR},
12    SESSION_COOKIE,
13};
14use log::{error, info, warn};
15use webapp::{
16    protocol::{model::Session, request::LoginSession, response::Login},
17    API_URL_LOGIN_SESSION,
18};
19use yew::{agent::Bridged, format::Cbor, html, prelude::*, services::fetch::FetchTask};
20use yew_router::{self, Route, RouterAgent};
21
22/// Data Model for the Root Component
23pub struct RootComponent {
24    child_component: RouterTarget,
25    cookie_service: CookieService,
26    fetch_task: Option<FetchTask>,
27    router_agent: Box<Bridge<RouterAgent<()>>>,
28    uikit_service: UIkitService,
29}
30
31/// Available message types to process
32pub enum Message {
33    Fetch(Response<Login>),
34    Route(Route<()>),
35}
36
37impl Component for RootComponent {
38    type Message = Message;
39    type Properties = ();
40
41    fn create(_: Self::Properties, mut link: ComponentLink<Self>) -> Self {
42        // Create needed services
43        let cookie_service = CookieService::new();
44        let mut fetch_task = None;
45        let mut router_agent = RouterAgent::bridge(link.send_back(Message::Route));
46        let uikit_service = UIkitService::new();
47
48        // Verify if a session cookie already exist and try to authenticate if so
49        if let Ok(token) = cookie_service.get(SESSION_COOKIE) {
50            fetch_task = fetch! {
51                LoginSession(Session::new(token)) => API_URL_LOGIN_SESSION,
52                link, Message::Fetch,
53                || {},
54                || {
55                    error!("Unable to create session login request");
56                    uikit_service.notify(REQUEST_ERROR, &NotificationStatus::Danger);
57                    cookie_service.remove(SESSION_COOKIE);
58                    router_agent.send(yew_router::Request::ChangeRoute(RouterTarget::Login.into()));
59                }
60            };
61        } else {
62            info!("No token found, routing to login");
63            router_agent.send(yew_router::Request::ChangeRoute(RouterTarget::Login.into()));
64        }
65
66        // Return the component
67        Self {
68            child_component: RouterTarget::Loading,
69            cookie_service,
70            fetch_task,
71            router_agent,
72            uikit_service,
73        }
74    }
75
76    fn change(&mut self, _: Self::Properties) -> ShouldRender {
77        true
78    }
79
80    fn update(&mut self, msg: Self::Message) -> ShouldRender {
81        match msg {
82            // Route to the appropriate child component
83            Message::Route(route) => self.child_component = route.into(),
84
85            // The message for all fetch responses
86            Message::Fetch(response) => {
87                let (meta, Cbor(body)) = response.into_parts();
88
89                // Check the response type
90                if meta.status.is_success() {
91                    match body {
92                        Ok(Login(Session { token })) => {
93                            info!("Session based login succeed");
94
95                            // Set the retrieved session cookie
96                            self.cookie_service.set(SESSION_COOKIE, &token);
97
98                            // Route to the content component
99                            self.router_agent.send(yew_router::Request::ChangeRoute(
100                                RouterTarget::Content.into(),
101                            ));
102                        }
103                        _ => {
104                            // Send an error notification to the user on any failure
105                            warn!("Got wrong session login response");
106                            self.uikit_service
107                                .notify(RESPONSE_ERROR, &NotificationStatus::Danger);
108                            self.router_agent
109                                .send(yew_router::Request::ChangeRoute(RouterTarget::Login.into()));
110                        }
111                    }
112                } else {
113                    // Remove the existing cookie
114                    warn!("Session login failed with status: {}", meta.status);
115                    self.cookie_service.remove(SESSION_COOKIE);
116                    self.router_agent
117                        .send(yew_router::Request::ChangeRoute(RouterTarget::Login.into()));
118                }
119
120                // Remove the ongoing task
121                self.fetch_task = None;
122            }
123        }
124        true
125    }
126}
127
128impl Renderable<RootComponent> for RootComponent {
129    fn view(&self) -> Html<Self> {
130        self.child_component.view()
131    }
132}
133
134impl Renderable<RootComponent> for RouterTarget {
135    fn view(&self) -> Html<RootComponent> {
136        match *self {
137            RouterTarget::Loading => {
138                html! {
139                    <div class="uk-position-center", uk-icon="icon: cloud-download; ratio: 3",></div>
140                }
141            }
142            RouterTarget::Login => {
143                html! {
144                    <LoginComponent:/>
145                }
146            }
147            RouterTarget::Content => {
148                html! {
149                    <ContentComponent:/>
150                }
151            }
152            RouterTarget::Error => {
153                html! {
154                    <div class="uk-position-center", uk-icon="icon: ban; ratio: 3",></div>
155                }
156            }
157        }
158    }
159}