diff --git a/visions/server/src/main.rs b/visions/server/src/main.rs
index f5443aa..1680dc4 100644
--- a/visions/server/src/main.rs
+++ b/visions/server/src/main.rs
@@ -54,6 +54,7 @@ impl result_extended::FatalError for FatalError {}
 fn parse_session_header(headers: HeaderMap) -> ResultExt<Option<SessionId>, AppError, FatalError> {
     match headers.get("Authorization") {
         Some(token) => {
+            println!("session token: {:?}", token);
             match token
                 .to_str()
                 .unwrap()
@@ -116,6 +117,7 @@ async fn main() {
             "/api/test/list-users",
             get(|headers: HeaderMap| {
                 auth_required(headers, || async {
+                    println!("list_users is about to return a bunch of stuff");
                     (
                         StatusCode::OK,
                         Some(vec![
diff --git a/visions/types/src/lib.rs b/visions/types/src/lib.rs
index 1dace5c..0ffc959 100644
--- a/visions/types/src/lib.rs
+++ b/visions/types/src/lib.rs
@@ -1,7 +1,7 @@
 use serde::{Deserialize, Serialize};
 use uuid::Uuid;
 
-#[derive(Deserialize, Serialize)]
+#[derive(Clone, Deserialize, PartialEq, Serialize)]
 #[serde(tag = "type", content = "content", rename_all = "kebab-case")]
 pub enum AccountStatus {
     Ok,
@@ -65,7 +65,7 @@ impl From<String> for UserId {
     }
 }
 
-#[derive(Deserialize, Serialize)]
+#[derive(Clone, Deserialize, PartialEq, Serialize)]
 pub struct UserOverview {
     pub id: UserId,
     pub name: String,
diff --git a/visions/ui/Taskfile.yml b/visions/ui/Taskfile.yml
new file mode 100644
index 0000000..88e9d26
--- /dev/null
+++ b/visions/ui/Taskfile.yml
@@ -0,0 +1,6 @@
+version: '3'
+
+tasks:
+  dev:
+    cmds:
+      - trunk serve --open
diff --git a/visions/ui/src/client.rs b/visions/ui/src/client.rs
index 990a8de..9b1936d 100644
--- a/visions/ui/src/client.rs
+++ b/visions/ui/src/client.rs
@@ -11,7 +11,7 @@ pub enum ClientError {
 
 pub trait Client {
     fn auth(&self, username: String, password: String) -> impl Future<Output = Result<AuthResponse, ClientError>>;
-    fn list_users(&self, session_id: SessionId) -> impl Future<Output = Result<Vec<UserOverview>, ClientError>>;
+    fn list_users(&self, session_id: &SessionId) -> impl Future<Output = Result<Vec<UserOverview>, ClientError>>;
 }
 
 pub struct Connection;
@@ -38,7 +38,18 @@ impl Client for Connection {
         }
     }
 
-    async fn list_users(&self, session_id: SessionId) -> Result<Vec<UserOverview>, ClientError> {
-        todo!()
+    async fn list_users(&self, session_id: &SessionId) -> Result<Vec<UserOverview>, ClientError> {
+        let response: Response = Request::get("/api/test/list-users")
+            .header("Content-Type", "application/json")
+            .header("Authorization", &format!("Bearer {}", session_id.as_str()))
+            .send()
+            .await
+            .unwrap();
+
+        if response.ok() {
+            Ok(serde_json::from_slice(&response.binary().await.unwrap()).unwrap())
+        } else {
+            Err(ClientError::Err(response.status()))
+        }
     }
 }
diff --git a/visions/ui/src/main.rs b/visions/ui/src/main.rs
index 1eab4cf..1b09023 100644
--- a/visions/ui/src/main.rs
+++ b/visions/ui/src/main.rs
@@ -1,7 +1,7 @@
 use std::rc::Rc;
 
 use gloo_console::log;
-use visions_types::AuthResponse;
+use visions_types::{AuthResponse, SessionId, UserOverview};
 use wasm_bindgen::JsCast;
 use web_sys::HtmlInputElement;
 use yew::prelude::*;
@@ -10,7 +10,7 @@ mod client;
 use client::*;
 
 struct AuthInfo {
-    session_id: Option<String>,
+    session_id: Option<SessionId>,
 }
 
 impl Default for AuthInfo {
@@ -31,7 +31,7 @@ impl Reducible for AuthInfo {
         // log!("reduce", action);
         match action {
             AuthAction::Auth(session_id) => Self {
-                session_id: Some(session_id),
+                session_id: Some(session_id.into()),
             }
             .into(),
             AuthAction::Unauth => Self { session_id: None }.into(),
@@ -92,6 +92,56 @@ fn Login(LoginProps { on_login }: &LoginProps) -> Html {
     }
 }
 
+#[derive(Properties, PartialEq)]
+struct LandingProps {
+    session_id: SessionId,
+}
+
+#[function_component]
+fn Landing(LandingProps { session_id }: &LandingProps) -> Html {
+    let user_ref = use_state(|| vec![]);
+
+    {
+        let user_ref = user_ref.clone();
+        let session_id = session_id.clone();
+        use_effect(move || {
+            wasm_bindgen_futures::spawn_local(async move {
+                let client = Connection::new();
+                match client.list_users(&session_id).await {
+                    Ok(users) => user_ref.set(users),
+                    Err(ClientError::Unauthorized) => todo!(),
+                    Err(ClientError::Err(status)) => {
+                        log!("error: {:?}", status);
+                        todo!()
+                    }
+                }
+            })
+        });
+    }
+
+    html! {
+        <div>
+            {"Landing Page"}
+            {user_ref.iter().map(|overview| {
+                let overview = overview.clone();
+                html! { <UserOverviewComponent overview={overview} /> }}).collect::<Vec<Html>>()
+            }
+        </div>
+    }
+}
+
+#[derive(Properties, PartialEq)]
+struct UserOverviewProps {
+    overview: UserOverview,
+}
+
+#[function_component]
+fn UserOverviewComponent(UserOverviewProps { overview }: &UserOverviewProps) -> Html {
+    html! {
+        <div> { overview.name.clone() } </div>
+    }
+}
+
 #[function_component]
 fn App() -> Html {
     let auth_info = use_reducer(AuthInfo::default);
@@ -116,10 +166,9 @@ fn App() -> Html {
         })
     };
 
-    if auth_info.session_id.is_some() {
-        html! { <p>{ "this is just a thing" }</p> }
-    } else {
-        html! { <Login on_login={on_login.clone()} /> }
+    match auth_info.session_id {
+        Some(ref session_id) => html! { <Landing session_id={session_id.clone()} /> },
+        None => html! { <Login on_login={on_login.clone()} /> },
     }
 }