diff --git a/src/components/app.rs b/src/components/app.rs
index 0ccd3a2..5597b7e 100644
--- a/src/components/app.rs
+++ b/src/components/app.rs
@@ -7,7 +7,7 @@ use dioxus::prelude::*;
 pub(crate) fn App() -> Element {
     rsx! {
         div {
-            class: "min-h-screen text-white bg-neutral-800",
+            class: "min-h-screen text-zinc-200 bg-zinc-800",
             Router::<Route> {}
         }
     }
diff --git a/src/components/bottom_panel.rs b/src/components/bottom_panel.rs
new file mode 100644
index 0000000..1332e78
--- /dev/null
+++ b/src/components/bottom_panel.rs
@@ -0,0 +1,44 @@
+use std::thread::sleep;
+use dioxus::prelude::*;
+use crate::components::navigation::Navigation;
+use crate::components::task_form::TaskForm;
+use crate::components::task_list::TaskList;
+use crate::models::category::Category;
+use crate::route::Route;
+
+#[component]
+pub(crate) fn BottomPanel(creating_task: bool) -> Element {
+    let mut expanded = use_signal(|| creating_task);
+    let navigation_expanded = use_signal(|| false);
+
+    use_effect(use_reactive(&creating_task, move |creating_task| {
+        if creating_task {
+            expanded.set(true);
+        } else {
+            spawn(async move {
+                async_std::task::sleep(std::time::Duration::from_millis(500)).await;
+                expanded.set(false);
+            });
+        }
+    }));
+
+    rsx! {
+        div {
+            class: format!(
+                "bg-zinc-700/50 rounded-t-xl border-t-zinc-600 border-t backdrop-blur drop-shadow-[0_-5px_10px_rgba(0,0,0,0.2)] transition-[height] duration-[500ms] ease-[cubic-bezier(0.79,0.14,0.15,0.86)] {}",
+                match (creating_task, navigation_expanded()) {
+                    (false, false) => "h-[64px]",
+                    (false, true) => "h-[128px]",
+                    (true, _) => "h-[448px]",
+                }
+            ),
+            if expanded() {
+                TaskForm {}
+            } else {
+                Navigation {
+                    expanded: navigation_expanded,
+                }
+            }
+        }
+    }
+}
diff --git a/src/components/category_input.rs b/src/components/category_input.rs
new file mode 100644
index 0000000..1ea3b8d
--- /dev/null
+++ b/src/components/category_input.rs
@@ -0,0 +1,104 @@
+use crate::models::category::Category;
+use crate::server::tasks::get_tasks_in_category;
+use chrono::NaiveDate;
+use dioxus::core_macro::rsx;
+use dioxus::dioxus_core::Element;
+use dioxus::prelude::*;
+use std::fmt::format;
+
+#[component]
+pub(crate) fn CategoryInput(selected_category: Signal<Category>, class: Option<&'static str>) -> Element {
+    rsx! {
+        div {
+            class: format!("flex flex-row gap-2 {}", class.unwrap_or("")),
+            button {
+                r#type: "button",
+                class: format!(
+                    "py-2 rounded-lg grow basis-0 {}",
+                    if selected_category() == Category::SomedayMaybe { "bg-zinc-500/50" }
+                    else { "bg-zinc-800/50" }
+                ),
+                onclick: move |_| {
+                    selected_category.set(Category::SomedayMaybe);
+                },
+                i {
+                    class: "fa-solid fa-question"
+                }
+            },
+            button {
+                r#type: "button",
+                class: format!(
+                    "py-2 rounded-lg grow basis-0 {}",
+                    if selected_category() == Category::LongTerm { "bg-zinc-500/50" }
+                    else { "bg-zinc-800/50" }
+                ),
+                onclick: move |_| {
+                    selected_category.set(Category::LongTerm);
+                },
+                i {
+                    class: "fa-solid fa-water"
+                }
+            },
+            button {
+                r#type: "button",
+                class: format!(
+                    "py-2 rounded-lg grow basis-0 {}",
+                    if let Category::WaitingFor(_) = selected_category() { "bg-zinc-500/50" }
+                    else { "bg-zinc-800/50" }
+                ),
+                onclick: move |_| {
+                    selected_category.set(Category::WaitingFor(String::new()));
+                },
+                i {
+                    class: "fa-solid fa-hourglass-half"
+                }
+            },
+            button {
+                r#type: "button",
+                class: format!(
+                    "py-2 rounded-lg grow basis-0 {}",
+                    if selected_category() == Category::NextSteps { "bg-zinc-500/50" }
+                    else { "bg-zinc-800/50" }
+                ),
+                onclick: move |_| {
+                    selected_category.set(Category::NextSteps);
+                },
+                i {
+                    class: "fa-solid fa-forward"
+                }
+            },
+            button {
+                r#type: "button",
+                class: format!(
+                    "py-2 rounded-lg grow basis-0 {}",
+                    if let Category::Calendar { .. } = selected_category() { "bg-zinc-500/50" }
+                    else { "bg-zinc-800/50" }
+                ),
+                onclick: move |_| {
+                    selected_category.set(Category::Calendar {
+                        date: NaiveDate::default(),
+                        reoccurrence: None,
+                        time: None,
+                    });
+                },
+                i {
+                    class: "fa-solid fa-calendar-days"
+                }
+            },
+            button {
+                r#type: "button",
+                class: format!(
+                    "py-2 rounded-lg grow basis-0 {}",
+                    if selected_category() == Category::Inbox { "bg-zinc-500/50" }
+                    else { "bg-zinc-800/50" }
+                ),
+                onclick: move |_| {
+                    selected_category.set(Category::Inbox);
+                },
+                i {
+                    class: "fa-solid fa-inbox"
+                }
+            }
+        }
+    }
+}
diff --git a/src/components/create_task_button.rs b/src/components/create_task_button.rs
new file mode 100644
index 0000000..b87a855
--- /dev/null
+++ b/src/components/create_task_button.rs
@@ -0,0 +1,19 @@
+use dioxus::prelude::*;
+use crate::components::task_list::TaskList;
+use crate::models::category::Category;
+use crate::route::Route;
+
+#[component]
+pub(crate) fn CreateTaskButton(creating: Signal<bool>) -> Element {
+    rsx! {
+        button {
+            class: "m-4 py-3 px-5 self-end text-center bg-zinc-300/50 rounded-xl border-t-zinc-200 border-t backdrop-blur drop-shadow-[0_-5px_10px_rgba(0,0,0,0.2)] text-2xl text-zinc-200",
+            onclick: move |_| {
+                creating.set(!creating());
+            },
+            i {
+                class: format!("min-w-6 fa-solid {}", if creating() { "fa-xmark" } else { "fa-plus" }),
+            }
+        }
+    }
+}
diff --git a/src/components/home.rs b/src/components/home.rs
index 827fad4..4dd9aac 100644
--- a/src/components/home.rs
+++ b/src/components/home.rs
@@ -1,13 +1,9 @@
-use crate::components::project_form::ProjectForm;
 use dioxus::core_macro::rsx;
 use dioxus::dioxus_core::Element;
 use dioxus::prelude::*;
-use crate::components::task_form::TaskForm;
 
 #[component]
 pub(crate) fn Home() -> Element {
     rsx! {
-        ProjectForm {}
-        TaskForm {}
     }
 }
diff --git a/src/components/layout.rs b/src/components/layout.rs
new file mode 100644
index 0000000..5ddb57c
--- /dev/null
+++ b/src/components/layout.rs
@@ -0,0 +1,30 @@
+use crate::components::bottom_panel::BottomPanel;
+use crate::components::navigation::Navigation;
+use crate::components::task_list::TaskList;
+use crate::models::category::Category;
+use crate::route::Route;
+use chrono::NaiveDate;
+use dioxus::core_macro::rsx;
+use dioxus::dioxus_core::Element;
+use dioxus::prelude::*;
+use crate::components::create_task_button::CreateTaskButton;
+use crate::components::sticky_bottom::StickyBottom;
+use crate::components::task_form::TaskForm;
+use crate::server::tasks::get_tasks_in_category;
+
+#[component]
+pub(crate) fn Layout() -> Element {
+    let creating_task = use_signal(|| false);
+    
+    rsx! {
+        Outlet::<Route> {}
+        StickyBottom {
+            CreateTaskButton {
+                creating: creating_task,
+            }
+            BottomPanel {
+                creating_task: creating_task(),
+            }
+        }
+    }
+}
diff --git a/src/components/mod.rs b/src/components/mod.rs
index c632a34..e60f716 100644
--- a/src/components/mod.rs
+++ b/src/components/mod.rs
@@ -2,3 +2,13 @@ pub(crate) mod app;
 pub(crate) mod home;
 pub(crate) mod project_form;
 pub(crate) mod task_form;
+pub(crate) mod task_list;
+pub(crate) mod pages;
+pub(crate) mod navigation;
+pub(crate) mod create_task_button;
+pub(crate) mod bottom_panel;
+pub(crate) mod sticky_bottom;
+pub(crate) mod category_input;
+pub(crate) mod reoccurrence_input;
+pub(crate) mod layout;
+mod navigation_item;
diff --git a/src/components/navigation.rs b/src/components/navigation.rs
new file mode 100644
index 0000000..8104116
--- /dev/null
+++ b/src/components/navigation.rs
@@ -0,0 +1,83 @@
+use crate::components::navigation_item::NavigationItem;
+use crate::components::task_list::TaskList;
+use crate::models::category::Category;
+use crate::route::Route;
+use dioxus::prelude::*;
+
+#[component]
+pub(crate) fn Navigation(expanded: Signal<bool>) -> Element {
+    rsx! {
+        div {
+            class: "grid grid-cols-5 justify-stretch",
+            button {
+                class: format!(
+                    "py-4 text-center text-2xl {}",
+                    if expanded() { "text-zinc-200" }
+                    else { "text-zinc-500" }
+                ),
+                onclick: move |_| expanded.set(!expanded()),
+                i {
+                    class: "fa-solid fa-bars"
+                }
+            },
+            NavigationItem {
+                route: Route::CategoryNextStepsPage,
+                i {
+                    class: "fa-solid fa-forward"
+                }
+            },
+            NavigationItem {
+                route: Route::CategoryCalendarPage,
+                i {
+                    class: "fa-solid fa-calendar-days"
+                }
+            },
+            NavigationItem {
+                route: Route::CategoryTodayPage,
+                i {
+                    class: "fa-solid fa-calendar-day"
+                }
+            },
+            NavigationItem {
+                route: Route::CategoryInboxPage,
+                i {
+                    class: "fa-solid fa-inbox"
+                }
+            },
+            {if expanded() {
+                rsx! {
+                    NavigationItem {
+                        route: Route::ProjectsPage,
+                        i {
+                            class: "fa-solid fa-list"
+                        }
+                    },
+                    NavigationItem {
+                        route: Route::CategoryTrashPage,
+                        i {
+                            class: "fa-solid fa-trash-can"
+                        }
+                    },
+                    NavigationItem {
+                        route: Route::CategoryDonePage,
+                        i {
+                            class: "fa-solid fa-check"
+                        }
+                    },
+                    NavigationItem {
+                        route: Route::CategoryLongTermPage,
+                        i {
+                            class: "fa-solid fa-water"
+                        }
+                    },
+                    NavigationItem {
+                        route: Route::CategoryWaitingForPage,
+                        i {
+                            class: "fa-solid fa-hourglass-half"
+                        }
+                    }
+                }
+            } else { None }}
+        }
+    }
+}
diff --git a/src/components/navigation_item.rs b/src/components/navigation_item.rs
new file mode 100644
index 0000000..3393245
--- /dev/null
+++ b/src/components/navigation_item.rs
@@ -0,0 +1,21 @@
+use dioxus::prelude::*;
+use crate::components::task_list::TaskList;
+use crate::models::category::Category;
+use crate::route::Route;
+
+#[component]
+pub(crate) fn NavigationItem(route: Route, children: Element) -> Element {
+    let current_route = use_route::<Route>();
+
+    rsx! {
+        Link {
+            to: route.clone(),
+            class: format!(
+                "py-4 text-center text-2xl {}",
+                if current_route == route { "text-zinc-200" }
+                else { "text-zinc-500" }
+            ),
+            children
+        }
+    }
+}
diff --git a/src/components/pages/category_calendar_page.rs b/src/components/pages/category_calendar_page.rs
new file mode 100644
index 0000000..6c434ce
--- /dev/null
+++ b/src/components/pages/category_calendar_page.rs
@@ -0,0 +1,31 @@
+use crate::components::bottom_panel::BottomPanel;
+use crate::components::navigation::Navigation;
+use crate::components::task_list::TaskList;
+use crate::models::category::Category;
+use crate::route::Route;
+use chrono::NaiveDate;
+use dioxus::core_macro::rsx;
+use dioxus::dioxus_core::Element;
+use dioxus::prelude::*;
+use crate::components::create_task_button::CreateTaskButton;
+use crate::components::sticky_bottom::StickyBottom;
+use crate::components::task_form::TaskForm;
+use crate::server::tasks::get_tasks_in_category;
+
+#[component]
+pub(crate) fn CategoryCalendarPage() -> Element {
+    let tasks = use_server_future(
+        move || get_tasks_in_category(Category::Calendar {
+            date: NaiveDate::default(),
+            reoccurrence: None,
+            time: None,
+        })
+    )?.unwrap().unwrap();
+
+    rsx! {
+        TaskList {
+            tasks: tasks,
+            class: "pb-36"
+        }
+    }
+}
diff --git a/src/components/pages/category_done_page.rs b/src/components/pages/category_done_page.rs
new file mode 100644
index 0000000..b524ec8
--- /dev/null
+++ b/src/components/pages/category_done_page.rs
@@ -0,0 +1,27 @@
+use crate::components::bottom_panel::BottomPanel;
+use crate::components::navigation::Navigation;
+use crate::components::task_list::TaskList;
+use crate::models::category::Category;
+use crate::route::Route;
+use chrono::NaiveDate;
+use dioxus::core_macro::rsx;
+use dioxus::dioxus_core::Element;
+use dioxus::prelude::*;
+use crate::components::create_task_button::CreateTaskButton;
+use crate::components::sticky_bottom::StickyBottom;
+use crate::components::task_form::TaskForm;
+use crate::server::tasks::get_tasks_in_category;
+
+#[component]
+pub(crate) fn CategoryDonePage() -> Element {
+    let tasks = use_server_future(
+        move || get_tasks_in_category(Category::Done)
+    )?.unwrap().unwrap();
+
+    rsx! {
+        TaskList {
+            tasks: tasks,
+            class: "pb-36"
+        }
+    }
+}
diff --git a/src/components/pages/category_inbox_page.rs b/src/components/pages/category_inbox_page.rs
new file mode 100644
index 0000000..ca592bc
--- /dev/null
+++ b/src/components/pages/category_inbox_page.rs
@@ -0,0 +1,27 @@
+use crate::components::bottom_panel::BottomPanel;
+use crate::components::navigation::Navigation;
+use crate::components::task_list::TaskList;
+use crate::models::category::Category;
+use crate::route::Route;
+use chrono::NaiveDate;
+use dioxus::core_macro::rsx;
+use dioxus::dioxus_core::Element;
+use dioxus::prelude::*;
+use crate::components::create_task_button::CreateTaskButton;
+use crate::components::sticky_bottom::StickyBottom;
+use crate::components::task_form::TaskForm;
+use crate::server::tasks::get_tasks_in_category;
+
+#[component]
+pub(crate) fn CategoryInboxPage() -> Element {
+    let tasks = use_server_future(
+        move || get_tasks_in_category(Category::Inbox)
+    )?.unwrap().unwrap();
+
+    rsx! {
+        TaskList {
+            tasks: tasks,
+            class: "pb-36"
+        }
+    }
+}
diff --git a/src/components/pages/category_long_term_page.rs b/src/components/pages/category_long_term_page.rs
new file mode 100644
index 0000000..4cc90de
--- /dev/null
+++ b/src/components/pages/category_long_term_page.rs
@@ -0,0 +1,27 @@
+use crate::components::bottom_panel::BottomPanel;
+use crate::components::navigation::Navigation;
+use crate::components::task_list::TaskList;
+use crate::models::category::Category;
+use crate::route::Route;
+use chrono::NaiveDate;
+use dioxus::core_macro::rsx;
+use dioxus::dioxus_core::Element;
+use dioxus::prelude::*;
+use crate::components::create_task_button::CreateTaskButton;
+use crate::components::sticky_bottom::StickyBottom;
+use crate::components::task_form::TaskForm;
+use crate::server::tasks::get_tasks_in_category;
+
+#[component]
+pub(crate) fn CategoryLongTermPage() -> Element {
+    let tasks = use_server_future(
+        move || get_tasks_in_category(Category::LongTerm)
+    )?.unwrap().unwrap();
+
+    rsx! {
+        TaskList {
+            tasks: tasks,
+            class: "pb-36"
+        }
+    }
+}
diff --git a/src/components/pages/category_next_steps_page.rs b/src/components/pages/category_next_steps_page.rs
new file mode 100644
index 0000000..bfbe17f
--- /dev/null
+++ b/src/components/pages/category_next_steps_page.rs
@@ -0,0 +1,27 @@
+use crate::components::bottom_panel::BottomPanel;
+use crate::components::navigation::Navigation;
+use crate::components::task_list::TaskList;
+use crate::models::category::Category;
+use crate::route::Route;
+use chrono::NaiveDate;
+use dioxus::core_macro::rsx;
+use dioxus::dioxus_core::Element;
+use dioxus::prelude::*;
+use crate::components::create_task_button::CreateTaskButton;
+use crate::components::sticky_bottom::StickyBottom;
+use crate::components::task_form::TaskForm;
+use crate::server::tasks::get_tasks_in_category;
+
+#[component]
+pub(crate) fn CategoryNextStepsPage() -> Element {
+    let tasks = use_server_future(
+        move || get_tasks_in_category(Category::NextSteps)
+    )?.unwrap().unwrap();
+
+    rsx! {
+        TaskList {
+            tasks: tasks,
+            class: "pb-36"
+        }
+    }
+}
diff --git a/src/components/pages/category_someday_maybe_page.rs b/src/components/pages/category_someday_maybe_page.rs
new file mode 100644
index 0000000..bb70a17
--- /dev/null
+++ b/src/components/pages/category_someday_maybe_page.rs
@@ -0,0 +1,27 @@
+use crate::components::bottom_panel::BottomPanel;
+use crate::components::navigation::Navigation;
+use crate::components::task_list::TaskList;
+use crate::models::category::Category;
+use crate::route::Route;
+use chrono::NaiveDate;
+use dioxus::core_macro::rsx;
+use dioxus::dioxus_core::Element;
+use dioxus::prelude::*;
+use crate::components::create_task_button::CreateTaskButton;
+use crate::components::sticky_bottom::StickyBottom;
+use crate::components::task_form::TaskForm;
+use crate::server::tasks::get_tasks_in_category;
+
+#[component]
+pub(crate) fn CategorySomedayMaybePage() -> Element {
+    let tasks = use_server_future(
+        move || get_tasks_in_category(Category::SomedayMaybe)
+    )?.unwrap().unwrap();
+
+    rsx! {
+        TaskList {
+            tasks: tasks,
+            class: "pb-36"
+        }
+    }
+}
diff --git a/src/components/pages/category_today_page.rs b/src/components/pages/category_today_page.rs
new file mode 100644
index 0000000..d01f477
--- /dev/null
+++ b/src/components/pages/category_today_page.rs
@@ -0,0 +1,39 @@
+use crate::components::bottom_panel::BottomPanel;
+use crate::components::create_task_button::CreateTaskButton;
+use crate::components::navigation::Navigation;
+use crate::components::sticky_bottom::StickyBottom;
+use crate::components::task_form::TaskForm;
+use crate::components::task_list::TaskList;
+use crate::models::category::Category;
+use crate::models::task::Task;
+use crate::route::Route;
+use crate::schema::tasks::category;
+use crate::server::tasks::get_tasks_in_category;
+use chrono::{Local, NaiveDate};
+use dioxus::core_macro::rsx;
+use dioxus::dioxus_core::Element;
+use dioxus::prelude::*;
+
+#[component]
+pub(crate) fn CategoryTodayPage() -> Element {
+    let tasks = use_server_future(
+        move || get_tasks_in_category(Category::Calendar {
+            date: NaiveDate::default(),
+            reoccurrence: None,
+            time: None,
+        })
+    )?.unwrap().unwrap().iter().filter(|task| {
+        if let Category::Calendar { date, .. } = task.category() {
+            *date == Local::now().date_naive()
+        } else {
+            panic!("Unexpected category.");
+        }
+    }).cloned().collect::<Vec<Task>>();
+
+    rsx! {
+        TaskList {
+            tasks: tasks,
+            class: "pb-36"
+        }
+    }
+}
diff --git a/src/components/pages/category_trash_page.rs b/src/components/pages/category_trash_page.rs
new file mode 100644
index 0000000..538c85d
--- /dev/null
+++ b/src/components/pages/category_trash_page.rs
@@ -0,0 +1,27 @@
+use crate::components::bottom_panel::BottomPanel;
+use crate::components::navigation::Navigation;
+use crate::components::task_list::TaskList;
+use crate::models::category::Category;
+use crate::route::Route;
+use chrono::NaiveDate;
+use dioxus::core_macro::rsx;
+use dioxus::dioxus_core::Element;
+use dioxus::prelude::*;
+use crate::components::create_task_button::CreateTaskButton;
+use crate::components::sticky_bottom::StickyBottom;
+use crate::components::task_form::TaskForm;
+use crate::server::tasks::get_tasks_in_category;
+
+#[component]
+pub(crate) fn CategoryTrashPage() -> Element {
+    let tasks = use_server_future(
+        move || get_tasks_in_category(Category::Trash)
+    )?.unwrap().unwrap();
+
+    rsx! {
+        TaskList {
+            tasks: tasks,
+            class: "pb-36"
+        }
+    }
+}
diff --git a/src/components/pages/category_waiting_for_page.rs b/src/components/pages/category_waiting_for_page.rs
new file mode 100644
index 0000000..3145bfc
--- /dev/null
+++ b/src/components/pages/category_waiting_for_page.rs
@@ -0,0 +1,27 @@
+use crate::components::bottom_panel::BottomPanel;
+use crate::components::navigation::Navigation;
+use crate::components::task_list::TaskList;
+use crate::models::category::Category;
+use crate::route::Route;
+use chrono::NaiveDate;
+use dioxus::core_macro::rsx;
+use dioxus::dioxus_core::Element;
+use dioxus::prelude::*;
+use crate::components::create_task_button::CreateTaskButton;
+use crate::components::sticky_bottom::StickyBottom;
+use crate::components::task_form::TaskForm;
+use crate::server::tasks::get_tasks_in_category;
+
+#[component]
+pub(crate) fn CategoryWaitingForPage() -> Element {
+    let tasks = use_server_future(
+        move || get_tasks_in_category(Category::WaitingFor(String::new()))
+    )?.unwrap().unwrap();
+
+    rsx! {
+        TaskList {
+            tasks: tasks,
+            class: "pb-36"
+        }
+    }
+}
diff --git a/src/components/pages/mod.rs b/src/components/pages/mod.rs
new file mode 100644
index 0000000..59bb5ef
--- /dev/null
+++ b/src/components/pages/mod.rs
@@ -0,0 +1,11 @@
+pub(crate) mod category_inbox_page;
+pub(crate) mod category_calendar_page;
+pub(crate) mod category_today_page;
+pub(crate) mod category_waiting_for_page;
+pub(crate) mod category_long_term_page;
+pub(crate) mod category_next_steps_page;
+pub(crate) mod category_someday_maybe_page;
+pub(crate) mod category_done_page;
+pub(crate) mod category_trash_page;
+pub(crate) mod not_found_page;
+pub(crate) mod projects_page;
diff --git a/src/components/pages/not_found_page.rs b/src/components/pages/not_found_page.rs
new file mode 100644
index 0000000..73e3ebd
--- /dev/null
+++ b/src/components/pages/not_found_page.rs
@@ -0,0 +1,11 @@
+use dioxus::prelude::*;
+use crate::components::task_list::TaskList;
+use crate::models::category::Category;
+use crate::route::Route;
+
+#[component]
+pub(crate) fn NotFoundPage(route: Vec<String>) -> Element {
+    rsx! {
+        {"404"}
+    }
+}
diff --git a/src/components/pages/projects_page.rs b/src/components/pages/projects_page.rs
new file mode 100644
index 0000000..5c8b018
--- /dev/null
+++ b/src/components/pages/projects_page.rs
@@ -0,0 +1,7 @@
+use dioxus::prelude::*;
+
+#[component]
+pub(crate) fn ProjectsPage() -> Element {
+    rsx! {
+    }
+}
diff --git a/src/components/reoccurrence_input.rs b/src/components/reoccurrence_input.rs
new file mode 100644
index 0000000..30099a3
--- /dev/null
+++ b/src/components/reoccurrence_input.rs
@@ -0,0 +1,79 @@
+use crate::models::category::{Category, Reoccurrence, ReoccurrenceInterval};
+use crate::server::tasks::get_tasks_in_category;
+use chrono::NaiveDate;
+use dioxus::core_macro::rsx;
+use dioxus::dioxus_core::Element;
+use dioxus::prelude::*;
+use std::fmt::format;
+
+#[component]
+pub(crate) fn ReoccurrenceIntervalInput(
+    reoccurrence_interval: Signal<Option<ReoccurrenceInterval>>,
+    class_buttons: Option<&'static str>
+) -> Element {
+    rsx! {
+        button {
+            r#type: "button",
+            class: format!(
+                "py-2 rounded-lg {} {}",
+                class_buttons.unwrap_or(""),
+                if reoccurrence_interval().is_none() { "bg-zinc-500/50" }
+                else { "bg-zinc-800/50" }
+            ),
+            onclick: move |_| {
+                reoccurrence_interval.set(None);
+            },
+            i {
+                class: "fa-solid fa-ban"
+            }
+        },
+        button {
+            r#type: "button",
+            class: format!(
+                "py-2 rounded-lg {} {}",
+                class_buttons.unwrap_or(""),
+                if let Some(ReoccurrenceInterval::Day) = reoccurrence_interval()
+                { "bg-zinc-500/50" }
+                else { "bg-zinc-800/50" }
+            ),
+            onclick: move |_| {
+                reoccurrence_interval.set(Some(ReoccurrenceInterval::Day))
+            },
+            i {
+                class: "fa-solid fa-sun"
+            }
+        },
+        button {
+            r#type: "button",
+            class: format!(
+                "py-2 rounded-lg {} {}",
+                class_buttons.unwrap_or(""),
+                if let Some(ReoccurrenceInterval::Month) = reoccurrence_interval()
+                { "bg-zinc-500/50" }
+                else { "bg-zinc-800/50" }
+            ),
+            onclick: move |_| {
+                reoccurrence_interval.set(Some(ReoccurrenceInterval::Month))
+            },
+            i {
+                class: "fa-solid fa-moon"
+            }
+        },
+        button {
+            r#type: "button",
+            class: format!(
+                "py-2 rounded-lg {} {}",
+                class_buttons.unwrap_or(""),
+                if let Some(ReoccurrenceInterval::Year) = reoccurrence_interval()
+                { "bg-zinc-500/50" }
+                else { "bg-zinc-800/50" }
+            ),
+            onclick: move |_| {
+                reoccurrence_interval.set(Some(ReoccurrenceInterval::Year))
+            },
+            i {
+                class: "fa-solid fa-earth-europe"
+            }
+        }
+    }
+}
diff --git a/src/components/sticky_bottom.rs b/src/components/sticky_bottom.rs
new file mode 100644
index 0000000..9dbb9d7
--- /dev/null
+++ b/src/components/sticky_bottom.rs
@@ -0,0 +1,14 @@
+use dioxus::prelude::*;
+use crate::components::task_list::TaskList;
+use crate::models::category::Category;
+use crate::route::Route;
+
+#[component]
+pub(crate) fn StickyBottom(children: Element) -> Element {
+    rsx! {
+        div {
+            class: "fixed bottom-0 left-0 right-0 flex flex-col",
+            {children}
+        }
+    }
+}
diff --git a/src/components/task_form.rs b/src/components/task_form.rs
index a280437..981dcd8 100644
--- a/src/components/task_form.rs
+++ b/src/components/task_form.rs
@@ -1,45 +1,65 @@
-use chrono::Duration;
-use crate::models::category::{CalendarTime, Category};
+use std::fmt::Display;
+use crate::components::category_input::CategoryInput;
+use crate::components::reoccurrence_input::ReoccurrenceIntervalInput;
+use crate::models::category::{CalendarTime, Category, Reoccurrence, ReoccurrenceInterval};
 use crate::models::task::NewTask;
 use crate::server::projects::get_projects;
 use crate::server::tasks::create_task;
+use chrono::{Duration, NaiveDate};
 use dioxus::core_macro::{component, rsx};
 use dioxus::dioxus_core::Element;
 use dioxus::prelude::*;
+use crate::route::Route;
+
+const REMINDER_OFFSETS: [Option<Duration>; 17] = [
+    None,
+    Some(Duration::days(1)),
+    Some(Duration::hours(12)),
+    Some(Duration::hours(11)),
+    Some(Duration::hours(10)),
+    Some(Duration::hours(9)),
+    Some(Duration::hours(8)),
+    Some(Duration::hours(7)),
+    Some(Duration::hours(6)),
+    Some(Duration::hours(5)),
+    Some(Duration::hours(4)),
+    Some(Duration::hours(3)),
+    Some(Duration::hours(2)),
+    Some(Duration::hours(1)),
+    Some(Duration::minutes(30)),
+    Some(Duration::minutes(10)),
+    Some(Duration::zero()),
+];
 
 #[component]
 pub(crate) fn TaskForm() -> Element {
-    let categories = vec![
-        Category::Inbox,
-        Category::SomedayMaybe,
-        Category::WaitingFor(String::new()),
-        Category::NextSteps,
-        Category::Calendar {
-            date: chrono::Local::now().date_naive(),
-            reoccurance_interval: None,
-            time: None,
-        },
-        Category::LongTerm,
-    ];
     let projects = use_server_future(get_projects)?.unwrap().unwrap();
 
-    let mut selected_category_index = use_signal::<usize>(|| 0);
-    let mut category_calendar_is_reoccurring = use_signal::<bool>(|| false);
-    let mut category_calendar_has_time = use_signal::<bool>(|| false);
-    let mut category_calendar_has_reminder = use_signal::<bool>(|| false);
+    let route = use_route::<Route>();
+    let mut selected_category = use_signal(|| match route { 
+        Route::CategorySomedayMaybePage => Category::SomedayMaybe,
+        Route::CategoryWaitingForPage => Category::WaitingFor(String::new()),
+        Route::CategoryNextStepsPage => Category::NextSteps,
+        Route::CategoryCalendarPage | Route::CategoryTodayPage => Category::Calendar {
+            date: NaiveDate::default(),
+            reoccurrence: None,
+            time: None,
+        },
+        Route::CategoryLongTermPage => Category::LongTerm,
+        _ => Category::Inbox,
+    });
+    let mut category_calendar_reoccurrence_interval = use_signal(|| None);
+    let mut category_calendar_has_time = use_signal(|| false);
+    let mut category_calendar_reminder_offset_index = use_signal(|| REMINDER_OFFSETS.len() - 1);
 
     rsx! {
         form {
             onsubmit: move |event| {
-                let categories = categories.clone();
                 async move {
                     let new_task = NewTask::new(
                         event.values().get("title").unwrap().as_value(),
                         event.values().get("deadline").unwrap().as_value().parse().ok(),
-                        match &categories[
-                            event.values().get("category_index").unwrap()
-                            .as_value().parse::<usize>().unwrap()
-                        ] {
+                        match &selected_category() {
                             Category::WaitingFor(_) => Category::WaitingFor(
                                 event.values().get("category_waiting_for").unwrap()
                                 .as_value()
@@ -47,24 +67,24 @@ pub(crate) fn TaskForm() -> Element {
                             Category::Calendar { .. } => Category::Calendar {
                                 date: event.values().get("category_calendar_date").unwrap()
                                 .as_value().parse().unwrap(),
-                                reoccurance_interval:
-                                event.values().get("category_calendar_is_reoccurring").map(
-                                    |_| Duration::days(
-                                        event.values().get("category_calendar_reoccurance_interval")
+                                reoccurrence: category_calendar_reoccurrence_interval().map(
+                                    |reoccurrence_interval| Reoccurrence::new(
+                                        event.values().get("category_calendar_date").unwrap()
+                                        .as_value().parse().unwrap(),
+                                        reoccurrence_interval,
+                                        event.values().get("category_calendar_reoccurrence_length")
                                         .unwrap().as_value().parse().unwrap()
                                     )
                                 ),
                                 time: event.values().get("category_calendar_time").unwrap()
-                                    .as_value().parse().ok().map(|time|
+                                .as_value().parse().ok().map(|time|
                                     CalendarTime::new(
                                         time,
-                                        event.values().get("category_calendar_has_reminder").map(
-                                            |_| Duration::minutes(
-                                                event.values()
-                                                .get("category_calendar_reminder_offset").unwrap()
-                                                .as_value().parse().unwrap()
-                                            )
-                                        )
+                                        REMINDER_OFFSETS[
+                                            event.values()
+                                            .get("category_calendar_reminder_offset_index").unwrap()
+                                            .as_value().parse::<usize>().unwrap()
+                                        ]
                                     )
                                 )
                             },
@@ -77,145 +97,207 @@ pub(crate) fn TaskForm() -> Element {
                 }
             },
             class: "p-4 flex flex-col gap-4",
-            input {
-                r#type: "text",
-                name: "title",
-                required: true,
-                placeholder: "title",
-                class: "p-2 bg-neutral-700 rounded",
-            },
-            select {
-                name: "category_index",
-                oninput: move |event| {
-                    selected_category_index.set(event.value().parse().unwrap());
-                },
-                class: "p-2 bg-neutral-700 rounded",
-                option {
-                    value: 0,
-                    "inbox"
-                },
-                option {
-                    value: 1,
-                    "someday maybe"
-                },
-                option {
-                    value: 2,
-                    "waiting for"
-                },
-                option {
-                    value: 3,
-                    "next steps"
-                },
-                option {
-                    value: 4,
-                    "calendar"
-                },
-                option {
-                    value: 5,
-                    "long term"
-                },
-            },
-            match categories[selected_category_index()] {
-                Category::WaitingFor(_) => rsx !{
-                    input {
-                        r#type: "text",
-                        name: "category_waiting_for",
-                        required: true,
-                        class: "p-2 bg-neutral-700 rounded",
+            div {
+                class: "flex flex-row items-center gap-3",
+                label {
+                    r#for: "input_title",
+                    class: "min-w-6 text-center",
+                    i {
+                        class: "fa-solid fa-pen-clip text-zinc-400/50"
                     },
                 },
-                Category::Calendar { .. } => rsx !{
-                    input {
-                        r#type: "date",
-                        name: "category_calendar_date",
-                        required: true,
-                        class: "p-2 bg-neutral-700 rounded",
+                input {
+                    r#type: "text",
+                    name: "title",
+                    required: true,
+                    class: "py-2 px-3 grow bg-zinc-800/50 rounded-lg",
+                    id: "input_title"
+                },
+            },
+            div {
+                class: "flex flex-row items-center gap-3",
+                label {
+                    r#for: "input_project",
+                    class: "min-w-6 text-center",
+                    i {
+                        class: "fa-solid fa-list text-zinc-400/50"
+                    }
+                },
+                select {
+                    name: "project_id",
+                    class: "px-3.5 py-2.5 bg-zinc-800/50 rounded-lg grow",
+                    id: "input_project",
+                    option {
+                        value: 0,
+                        "None"
                     },
+                    for project in projects {
+                        option {
+                            value: project.id().to_string(),
+                            {project.title()}
+                        }
+                    }
+                },
+            },
+            div {
+                class: "flex flex-row items-center gap-3",
+                label {
+                    r#for: "input_deadline",
+                    class: "min-w-6 text-center",
+                    i {
+                        class: "fa-solid fa-bomb text-zinc-400/50"
+                    }
+                },
+                input {
+                    r#type: "date",
+                    name: "deadline",
+                    class: "py-2 px-3 bg-zinc-800/50 rounded-lg grow basis-0",
+                    id: "input_deadline"
+                }
+            },
+            div {
+                class: "flex flex-row items-center gap-3",
+                label {
+                    class: "min-w-6 text-center",
+                    i {
+                        class: "fa-solid fa-layer-group text-zinc-400/50"
+                    }
+                },
+                CategoryInput {
+                    selected_category: selected_category.clone(),
+                    class: "grow"
+                }
+            }
+            match selected_category() {
+                Category::WaitingFor(_) => rsx! {
                     div {
-                        input {
-                            r#type: "checkbox",
-                            name: "category_calendar_is_reoccurring",
-                            id: "category_calendar_is_reoccurring",
-                            onchange: move |event| {
-                                category_calendar_is_reoccurring.set(event.checked());
+                        class: "flex flex-row items-center gap-3",
+                        label {
+                            r#for: "input_deadline",
+                            class: "min-w-6 text-center",
+                            i {
+                                class: "fa-solid fa-hourglass-end text-zinc-400/50"
                             }
                         },
-                        label {
-                            r#for: "category_calendar_is_reoccurring",
-                            " is reoccurring"
-                        }
-                    },
-                    if category_calendar_is_reoccurring() {
                         input {
-                            r#type: "number",
-                            name: "category_calendar_reoccurance_interval",
+                            r#type: "text",
+                            name: "category_waiting_for",
                             required: true,
-                            min: 1,
-                            placeholder: "reoccurance interval (days)",
-                            class: "p-2 bg-neutral-700 rounded",
+                            class: "py-2 px-3 bg-zinc-800/50 rounded-lg grow",
+                            id: "input_category_waiting_for"
+                        },
+                    }
+                },
+                Category::Calendar { .. } => rsx! {
+                    div {
+                        class: "flex flex-row items-center gap-3",
+                        label {
+                            r#for: "input_category_calendar_date",
+                            class: "min-w-6 text-center",
+                            i {
+                                class: "fa-solid fa-clock text-zinc-400/50"
+                            }
+                        },
+                        div {
+                            class: "grow flex flex-row gap-2",
+                            input {
+                                r#type: "date",
+                                name: "category_calendar_date",
+                                required: true,
+                                initial_value: chrono::Local::now().format("%Y-%m-%d").to_string(),
+                                class: "py-2 px-3 bg-zinc-800/50 rounded-lg grow",
+                                id: "input_category_calendar_date"
+                            },
+                            input {
+                                r#type: "time",
+                                name: "category_calendar_time",
+                                class: "py-2 px-3 bg-zinc-800/50 rounded-lg grow",
+                                id: "input_category_calendar_time",
+                                oninput: move |event| {
+                                    category_calendar_has_time.set(!event.value().is_empty());
+                                }
+                            }
                         }
                     },
-                    input {
-                        r#type: "time",
-                        name: "category_calendar_time",
-                        class: "p-2 bg-neutral-700 rounded",
-                        oninput: move |event| {
-                            category_calendar_has_time.set(!event.value().is_empty());
+                    div {
+                        class: "flex flex-row items-center gap-3",
+                        label {
+                            r#for: "category_calendar_reoccurrence_length",
+                            class: "min-w-6 text-center",
+                            i {
+                                class: "fa-solid fa-repeat text-zinc-400/50"
+                            }
+                        },
+                        div {
+                            class: "grow grid grid-cols-6 gap-2",
+                            ReoccurrenceIntervalInput {
+                                reoccurrence_interval: category_calendar_reoccurrence_interval
+                            },
+                            input {
+                                r#type: "number",
+                                inputmode: "numeric",
+                                name: "category_calendar_reoccurrence_length",
+                                disabled: category_calendar_reoccurrence_interval().is_none(),
+                                required: true,
+                                min: 1,
+                                initial_value:
+                                if category_calendar_reoccurrence_interval().is_none() { "" }
+                                else { "1" },
+                                class: "py-2 px-3 bg-zinc-800/50 rounded-lg col-span-2 text-right",
+                                id: "category_calendar_reoccurrence_length"
+                            }
                         }
                     },
                     if category_calendar_has_time() {
                         div {
+                            class: "flex flex-row items-center gap-3",
+                            label {
+                                r#for: "category_calendar_reminder_offset_index",
+                                class: "min-w-6 text-center",
+                                i {
+                                    class: "fa-solid fa-bell text-zinc-400/50"
+                                }
+                            },
                             input {
-                                r#type: "checkbox",
-                                name: "category_calendar_has_reminder",
-                                value: 0,
+                                r#type: "range",
+                                name: "category_calendar_reminder_offset_index",
+                                min: 0,
+                                max: REMINDER_OFFSETS.len() as i64 - 1,
+                                initial_value: REMINDER_OFFSETS.len() as i64 - 1,
+                                class: "grow input-range-reverse",
                                 id: "category_calendar_has_reminder",
-                                onchange: move |event| {
-                                    category_calendar_has_reminder.set(event.checked());
+                                oninput: move |event| {
+                                    category_calendar_reminder_offset_index.set(
+                                        event.value().parse().unwrap()
+                                    );
                                 }
                             },
                             label {
-                                r#for: "category_calendar_has_reminder",
-                                " set a reminder"
+                                r#for: "category_calendar_reminder_offset_index",
+                                class: "pr-3 min-w-16 text-right",
+                                {REMINDER_OFFSETS[category_calendar_reminder_offset_index()].map(
+                                    |offset| if offset.num_hours() < 1 {
+                                        format!("{} min", offset.num_minutes())
+                                    } else {
+                                        format!("{} h", offset.num_hours())
+                                    }
+                                ).unwrap_or_else(|| "none".to_string())}
                             }
                         }
                     }
-                    if category_calendar_has_reminder() {
-                        input {
-                            r#type: "number",
-                            name: "category_calendar_reminder_offset",
-                            required: true,
-                            min: 0,
-                            placeholder: "reminder offset (minutes)",
-                            class: "p-2 bg-neutral-700 rounded",
-                        }
-                    }
                 },
                 _ => None
             },
-            input {
-                r#type: "date",
-                name: "deadline",
-                class: "p-2 bg-neutral-700 rounded",
-            },
-            select {
-                name: "project_id",
-                class: "p-2 bg-neutral-700 rounded",
-                option {
-                    value: 0,
-                    "none"
-                },
-                for project in projects {
-                    option {
-                        value: project.id().to_string(),
-                        {project.title()}
+            div {
+                class: "flex flex-row justify-end mt-auto",
+                button {
+                    r#type: "submit",
+                    class: "py-2 px-4 bg-zinc-300/50 rounded-lg",
+                    i {
+                        class: "fa-solid fa-floppy-disk"
                     }
                 }
-            },
-            button {
-                r#type: "submit",
-                "create"
             }
         }
-}
+    }
 }
diff --git a/src/components/task_list.rs b/src/components/task_list.rs
new file mode 100644
index 0000000..0612a2e
--- /dev/null
+++ b/src/components/task_list.rs
@@ -0,0 +1,66 @@
+use crate::models::category::Category;
+use crate::models::task::Task;
+use crate::server::tasks::get_tasks_in_category;
+use dioxus::core_macro::rsx;
+use dioxus::dioxus_core::Element;
+use dioxus::prelude::*;
+
+#[component]
+pub(crate) fn TaskList(tasks: Vec<Task>, class: Option<&'static str>) -> Element {
+    rsx! {
+        div {
+            class: format!("pt-3 px-8 flex flex-col {}", class.unwrap_or("")),
+            for task in tasks {
+                div {
+                    class: format!(
+                        "pt-5 {} flex flex-row gap-4",
+                        if task.deadline().is_some() {
+                            "pb-0.5"
+                        } else if let Category::Calendar { time, .. } = task.category() {
+                            if time.is_some() {
+                                "pb-0.5"
+                            } else {
+                                "pb-5"
+                            }
+                        } else {
+                            "pb-5"
+                        }
+                    ),
+                    i {
+                        class: "fa-regular fa-square text-3xl text-zinc-600",
+                    },
+                    div {
+                        class: "flex flex-col",
+                        div {
+                            class: "mt-1 grow",
+                            {task.title()}
+                        },
+                        div {
+                            class: "flex flex-row gap-3",
+                            if let Some(deadline) = task.deadline() {
+                                div {
+                                    class: "text-sm text-zinc-400",
+                                    i {
+                                        class: "fa-solid fa-bomb"
+                                    },
+                                    {deadline.format(" %m. %d.").to_string()}
+                                }
+                            }
+                            if let Category::Calendar { time, .. } = task.category() {
+                                if let Some(calendar_time) = time {
+                                    div {
+                                        class: "text-sm text-zinc-400",
+                                        i {
+                                            class: "fa-solid fa-clock"
+                                        },
+                                        {calendar_time.time().format(" %k:%M").to_string()}
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/models/category.rs b/src/models/category.rs
index c6ca42c..ad35ba6 100644
--- a/src/models/category.rs
+++ b/src/models/category.rs
@@ -1,10 +1,12 @@
+use crate::schema::tasks;
 use chrono::{Duration, NaiveDate, NaiveTime};
 use diesel::deserialize::FromSql;
 use diesel::pg::{Pg, PgValue};
 use diesel::serialize::{Output, ToSql};
-use diesel::sql_types::Jsonb;
-use diesel::{AsExpression, FromSqlRow};
+use diesel::sql_types::{Bool, Jsonb};
+use diesel::{AsExpression, BoxableExpression, FromSqlRow, PgJsonbExpressionMethods};
 use serde::{Deserialize, Serialize};
+use serde_json::json;
 use serde_with::DurationSeconds;
 use std::io::Write;
 
@@ -18,8 +20,7 @@ pub enum Category {
     NextSteps,
     Calendar {
         date: NaiveDate,
-        #[serde_as(as = "Option<DurationSeconds<i64>>")]
-        reoccurance_interval: Option<Duration>,
+        reoccurrence: Option<Reoccurrence>,
         time: Option<CalendarTime>,
     },
     LongTerm,
@@ -27,17 +28,26 @@ pub enum Category {
     Trash,
 }
 
-#[serde_with::serde_as]
-#[derive(Serialize, Deserialize, Clone, Debug)]
-pub struct CalendarTime {
-    time: NaiveTime,
-    #[serde_as(as = "Option<DurationSeconds<i64>>")]
-    reminder_offset: Option<Duration>,
+impl Category {
+    pub fn eq_sql_predicate(&self) -> Box<dyn BoxableExpression<tasks::table, Pg, SqlType=Bool>> {
+        use crate::schema::tasks::dsl::*;
+
+        match self {
+            Category::Inbox => Box::new(category.contains(json!("Inbox"))),
+            Category::SomedayMaybe => Box::new(category.contains(json!("SomedayMaybe"))),
+            Category::WaitingFor(_) => Box::new(category.has_key("WaitingFor")),
+            Category::NextSteps => Box::new(category.contains(json!("NextSteps"))),
+            Category::Calendar { .. } => Box::new(category.has_key("Calendar")),
+            Category::LongTerm => Box::new(category.contains(json!("LongTerm"))),
+            Category::Done => Box::new(category.contains(json!("Done"))),
+            Category::Trash => Box::new(category.contains(json!("Trash"))),
+        }
+    }
 }
 
-impl CalendarTime {
-    pub fn new(time: NaiveTime, reminder_offset: Option<Duration>) -> Self {
-        Self { time, reminder_offset }
+impl PartialEq for Category {
+    fn eq(&self, other: &Self) -> bool {
+        std::mem::discriminant(self) == std::mem::discriminant(other)
     }
 }
 
@@ -63,3 +73,53 @@ impl FromSql<Jsonb, Pg> for Category {
         serde_json::from_str(str).map_err(Into::into)
     }
 }
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub enum ReoccurrenceInterval {
+    Day,
+    Month,
+    Year,
+}
+
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct Reoccurrence {
+    start_date: NaiveDate,
+    interval: ReoccurrenceInterval,
+    length: u32,
+}
+
+impl Reoccurrence {
+    pub fn new(start_date: NaiveDate, interval: ReoccurrenceInterval, length: u32) -> Self {
+        Self { start_date, interval, length }
+    }
+    
+    pub fn interval(&self) -> &ReoccurrenceInterval {
+        &self.interval
+    }
+
+    pub fn length(&self) -> u32 {
+        self.length
+    }
+}
+
+#[serde_with::serde_as]
+#[derive(Serialize, Deserialize, Clone, Debug)]
+pub struct CalendarTime {
+    time: NaiveTime,
+    #[serde_as(as = "Option<DurationSeconds<i64>>")]
+    reminder_offset: Option<Duration>,
+}
+
+impl CalendarTime {
+    pub fn new(time: NaiveTime, reminder_offset: Option<Duration>) -> Self {
+        Self { time, reminder_offset }
+    }
+    
+    pub fn time(&self) -> NaiveTime {
+        self.time
+    }
+
+    pub fn reminder_offset(&self) -> Option<Duration> {
+        self.reminder_offset
+    }
+}
diff --git a/src/models/task.rs b/src/models/task.rs
index d67256a..058874d 100644
--- a/src/models/task.rs
+++ b/src/models/task.rs
@@ -7,7 +7,7 @@ use crate::schema::tasks;
 const TITLE_LENGTH_MIN: u64 = 1;
 const TITLE_LENGTH_MAX: u64 = 255;
 
-#[derive(Queryable, Selectable, Serialize, Deserialize, Clone, Debug)]
+#[derive(Queryable, Selectable, Serialize, Deserialize, PartialEq, Clone, Debug)]
 #[diesel(table_name = crate::schema::tasks)]
 #[diesel(check_for_backend(diesel::pg::Pg))]
 pub struct Task {
diff --git a/src/route/mod.rs b/src/route/mod.rs
index 275f297..8beedcf 100644
--- a/src/route/mod.rs
+++ b/src/route/mod.rs
@@ -1,8 +1,48 @@
 use crate::components::home::Home;
+use crate::components::pages::category_inbox_page::CategoryInboxPage;
+use crate::components::pages::category_next_steps_page::CategoryNextStepsPage;
+use crate::components::pages::category_today_page::CategoryTodayPage;
+use crate::components::pages::category_trash_page::CategoryTrashPage;
+use crate::components::pages::category_waiting_for_page::CategoryWaitingForPage;
+use crate::components::pages::category_someday_maybe_page::CategorySomedayMaybePage;
+use crate::components::pages::category_done_page::CategoryDonePage;
+use crate::components::pages::category_calendar_page::CategoryCalendarPage;
+use crate::components::pages::category_long_term_page::CategoryLongTermPage;
+use crate::components::pages::projects_page::ProjectsPage;
+use crate::components::pages::not_found_page::NotFoundPage;
+use crate::components::layout::Layout;
 use dioxus::prelude::*;
+use crate::models::category::Category;
 
 #[derive(Clone, Routable, Debug, PartialEq)]
+#[rustfmt::skip]
 pub(crate) enum Route {
-    #[route("/")]
-    Home {},
+    #[layout(Layout)]
+        #[redirect("/", || Route::CategoryTodayPage {})]
+        #[route("/today")]
+        CategoryTodayPage,
+        #[route("/inbox")]
+        CategoryInboxPage,
+        #[route("/someday-maybe")]
+        CategorySomedayMaybePage,
+        #[route("/waiting-for")]
+        CategoryWaitingForPage,
+        #[route("/next-steps")]
+        CategoryNextStepsPage,
+        #[route("/calendar")]
+        CategoryCalendarPage,
+        #[route("/long-term")]
+        CategoryLongTermPage,
+        #[route("/done")]
+        CategoryDonePage,
+        #[route("/trash")]
+        CategoryTrashPage,
+        #[route("/projects")]
+        ProjectsPage,
+    #[end_layout]
+    #[redirect("/", || Route::CategoryTodayPage)]
+    #[route("/:..route")]
+    NotFoundPage {
+        route: Vec<String>,
+    },
 }
diff --git a/src/server/tasks.rs b/src/server/tasks.rs
index 0b7c086..62c383a 100644
--- a/src/server/tasks.rs
+++ b/src/server/tasks.rs
@@ -2,10 +2,11 @@ use crate::errors::error::Error;
 use crate::errors::error_vec::ErrorVec;
 use crate::models::task::{NewTask, Task};
 use crate::server::database_connection::establish_database_connection;
-use diesel::{RunQueryDsl, SelectableHelper};
+use diesel::{QueryDsl, RunQueryDsl, SelectableHelper};
 use dioxus::prelude::*;
 use validator::Validate;
 use crate::errors::task_create_error::TaskCreateError;
+use crate::models::category::Category;
 
 #[server]
 pub(crate) async fn create_task(new_task: NewTask)
@@ -43,3 +44,24 @@ pub(crate) async fn create_task(new_task: NewTask)
 
     Ok(new_task)
 }
+
+#[server]
+pub(crate) async fn get_tasks_in_category(filtered_category: Category)
+    -> Result<Vec<Task>, ServerFnError<ErrorVec<Error>>> {
+    use crate::schema::tasks::dsl::*;
+
+    let mut connection = establish_database_connection()
+        .map_err::<ErrorVec<Error>, _>(
+            |_| vec![Error::ServerInternal].into()
+        )?;
+
+    let results = tasks
+        .select(Task::as_select())
+        .filter(filtered_category.eq_sql_predicate())
+        .load::<Task>(&mut connection)
+        .map_err::<ErrorVec<Error>, _>(
+            |_| vec![Error::ServerInternal].into()
+        )?;
+
+    Ok(results)
+}