Create sidebar layout and begin on structure of pages for projects and documents

This commit is contained in:
Nicole Tietz-Sokolskaya 2024-04-23 21:42:29 -04:00
parent 5b117e9a8c
commit 35c75cdc5c
11 changed files with 380 additions and 1080 deletions

116
Cargo.lock generated
View file

@ -341,6 +341,15 @@ dependencies = [
"num-traits",
]
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "1.3.2"
@ -611,6 +620,15 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
name = "crc32fast"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.12"
@ -792,7 +810,7 @@ checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"redox_syscall 0.4.1",
"windows-sys 0.52.0",
]
@ -828,6 +846,16 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fs2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "fsevent-sys"
version = "4.1.0"
@ -892,7 +920,7 @@ checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
dependencies = [
"futures-core",
"lock_api",
"parking_lot",
"parking_lot 0.12.1",
]
[[package]]
@ -942,6 +970,15 @@ dependencies = [
"slab",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]]
name = "generic-array"
version = "0.14.7"
@ -1244,6 +1281,15 @@ dependencies = [
"libc",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "itertools"
version = "0.12.1"
@ -1457,7 +1503,7 @@ dependencies = [
"crossbeam-utils",
"futures-util",
"once_cell",
"parking_lot",
"parking_lot 0.12.1",
"quanta",
"rustc_version",
"skeptic",
@ -1633,6 +1679,17 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core 0.8.6",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
@ -1640,7 +1697,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
"parking_lot_core 0.9.9",
]
[[package]]
name = "parking_lot_core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall 0.2.16",
"smallvec",
"winapi",
]
[[package]]
@ -1651,7 +1722,7 @@ checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"redox_syscall 0.4.1",
"smallvec",
"windows-targets 0.48.5",
]
@ -1730,6 +1801,7 @@ dependencies = [
"axum",
"axum-htmx",
"axum-login",
"bincode",
"clap",
"dotenvy",
"env_logger",
@ -1738,6 +1810,7 @@ dependencies = [
"rand",
"sea-orm",
"serde",
"sled",
"thiserror",
"tokio",
"tower-http",
@ -1929,6 +2002,15 @@ dependencies = [
"bitflags 2.4.2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
@ -2430,6 +2512,22 @@ dependencies = [
"autocfg",
]
[[package]]
name = "sled"
version = "0.34.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
dependencies = [
"crc32fast",
"crossbeam-epoch",
"crossbeam-utils",
"fs2",
"fxhash",
"libc",
"log",
"parking_lot 0.11.2",
]
[[package]]
name = "smallvec"
version = "1.13.1"
@ -2884,7 +2982,7 @@ dependencies = [
"libc",
"mio",
"num_cpus",
"parking_lot",
"parking_lot 0.12.1",
"pin-project-lite",
"signal-hook-registry",
"socket2",
@ -2972,7 +3070,7 @@ dependencies = [
"cookie",
"futures-util",
"http",
"parking_lot",
"parking_lot 0.12.1",
"pin-project-lite",
"tower-layer",
"tower-service",
@ -3044,7 +3142,7 @@ dependencies = [
"base64 0.22.0",
"futures",
"http",
"parking_lot",
"parking_lot 0.12.1",
"rand",
"serde",
"serde_json",
@ -3363,7 +3461,7 @@ version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9"
dependencies = [
"redox_syscall",
"redox_syscall 0.4.1",
"wasite",
]

View file

@ -13,6 +13,7 @@ async-trait = "0.1.78"
axum = "0.7.4"
axum-htmx = { version = "0.5.0", features = ["guards", "serde"] }
axum-login = "0.14.0"
bincode = "1.3.3"
clap = { version = "4.5.3", features = ["derive", "env"] }
dotenvy = "0.15.7"
env_logger = "0.11.3"
@ -21,6 +22,7 @@ minijinja-autoreload = "1.0.14"
rand = "0.8.5"
sea-orm = { version = "0.12.15", features = ["sqlx-sqlite", "macros", "runtime-tokio-rustls"] }
serde = { version = "1.0.197", features = ["derive"] }
sled = "0.34.7"
thiserror = "1.0.58"
tokio = { version = "1.36.0", features = ["rt", "full"] }
tower-http = { version = "0.5.2", features = ["fs", "trace"] }

View file

@ -1,12 +1,28 @@
use axum::response::Redirect;
use axum_login::AuthSession;
use crate::prelude::*;
use crate::{models::Project, prelude::*};
pub async fn home_page(State(ctx): State<Context>, auth_session: AuthSession<Context>) -> Response {
if let Some(user) = auth_session.user {
let projects: Vec<Project> = vec![
Project {
id: 1,
owner_id: 1,
name: "Blog posts".to_owned(),
key: "BLOG".to_owned(),
},
Project {
id: 2,
owner_id: 1,
name: "Bugs (Pique)".to_owned(),
key: "BUG".to_owned(),
},
];
let values = context! {
user => user,
projects => projects,
};
ctx.render_resp("home.html", values)

View file

@ -4,8 +4,10 @@ pub mod db;
pub mod entity;
pub mod handler;
pub mod logging;
pub mod models;
pub mod password;
pub mod prelude;
pub mod serialize;
pub mod server;
pub mod session;
pub mod templates;

23
src/models.rs Normal file
View file

@ -0,0 +1,23 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Project {
pub id: u64,
pub owner_id: i32,
pub name: String,
// The key is the short code, like BUG, which is used to refer to a project
// quickly and to display it more compactly. This must be unique across the
// projects a user owns.
pub key: String,
}
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct Document {
pub id: u64,
pub project_id: u64,
pub title: String,
pub content: String,
}

16
src/serialize.rs Normal file
View file

@ -0,0 +1,16 @@
use bincode::{DefaultOptions, Options};
use serde::{Deserialize, Serialize};
fn bincode_options() -> impl Options {
DefaultOptions::new().with_big_endian()
}
pub fn serialize<T: ?Sized + Serialize>(value: &T) -> Result<Vec<u8>, bincode::Error> {
let options = bincode_options();
options.serialize(value)
}
pub fn deserialize<'a, T: Deserialize<'a>>(bytes: &'a [u8]) -> Result<T, bincode::Error> {
let options = bincode_options();
options.deserialize(bytes)
}

File diff suppressed because it is too large Load diff

View file

@ -1,46 +1,46 @@
let mobile_open_button = document.getElementById("mobile-menu-open-button");
let mobile_close_button = document.getElementById("mobile-menu-close-button");
let mobile_menu = document.getElementById("mobile-menu");
const profile_button = document.getElementById("profile-menu-button");
let profile_dropdown = document.getElementById("profile-dropdown");
function toggle_mobile_menu() {
if (mobile_open_button.classList.contains("block")) {
mobile_menu.classList.remove("hidden");
mobile_open_button.classList.remove("block");
mobile_open_button.classList.add("hidden");
mobile_close_button.classList.remove("hidden");
mobile_close_button.classList.add("block");
} else {
mobile_menu.classList.add("hidden");
mobile_close_button.classList.remove("block");
mobile_close_button.classList.add("hidden");
mobile_open_button.classList.remove("hidden");
mobile_open_button.classList.add("block");
}
}
function toggle_profile_dropdown() {
if (profile_dropdown.classList.contains("hidden")) {
profile_dropdown.classList.remove("hidden");
} else {
profile_dropdown.classList.add("hidden");
}
}
function hide_profile_dropdown() {
let profile_dropdown = document.getElementById("profile-dropdown");
if (!profile_dropdown.classList.contains("hidden")) {
profile_dropdown.classList.add("hidden");
}
}
document.addEventListener("click", (event) => {
if (!profile_button.contains(event.target) && !profile_dropdown.contains(event.target)) {
profile_dropdown.classList.add("hidden");
}
});
//let mobile_open_button = document.getElementById("mobile-menu-open-button");
//let mobile_close_button = document.getElementById("mobile-menu-close-button");
//let mobile_menu = document.getElementById("mobile-menu");
//
//const profile_button = document.getElementById("profile-menu-button");
//let profile_dropdown = document.getElementById("profile-dropdown");
//
//function toggle_mobile_menu() {
//
// if (mobile_open_button.classList.contains("block")) {
// mobile_menu.classList.remove("hidden");
// mobile_open_button.classList.remove("block");
// mobile_open_button.classList.add("hidden");
// mobile_close_button.classList.remove("hidden");
// mobile_close_button.classList.add("block");
// } else {
// mobile_menu.classList.add("hidden");
// mobile_close_button.classList.remove("block");
// mobile_close_button.classList.add("hidden");
// mobile_open_button.classList.remove("hidden");
// mobile_open_button.classList.add("block");
// }
//}
//
//function toggle_profile_dropdown() {
//
// if (profile_dropdown.classList.contains("hidden")) {
// profile_dropdown.classList.remove("hidden");
// } else {
// profile_dropdown.classList.add("hidden");
// }
//}
//
//function hide_profile_dropdown() {
// let profile_dropdown = document.getElementById("profile-dropdown");
//
// if (!profile_dropdown.classList.contains("hidden")) {
// profile_dropdown.classList.add("hidden");
// }
//}
//
//document.addEventListener("click", (event) => {
// if (!profile_button.contains(event.target) && !profile_dropdown.contains(event.target)) {
// profile_dropdown.classList.add("hidden");
// }
//});

View file

@ -0,0 +1,85 @@
<div class="fixed inset-y-0 z-50 flex w-72 flex-col">
<div class="flex grow flex-col gap-y-5 overflow-y-auto border-r border-gray-20 bg-white px-6">
<div class="flex h-16 shrink-0 items-center">
<span class="h-8 w-auto text-xl">⛰️</span>
</div>
<nav class="flex flex-1 flex-col">
<ul role="list" class="flex flex-1 flex-col gap-y-7">
<li>
<ul role="list" class="-mx-2 space-y-1">
<li>
<!-- Current: "bg-gray-50 text-indigo-600", Default: "text-gray-700 hover:text-indigo-600 hover:bg-gray-50" -->
<a href="#" class="bg-gray-50 text-indigo-600 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
<svg class="h-6 w-6 shrink-0 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
</svg>
Dashboard
</a>
</li>
<li>
<a href="#" class="text-gray-700 hover:text-indigo-600 hover:bg-gray-50 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
<svg class="h-6 w-6 shrink-0 text-gray-400 group-hover:text-indigo-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 014.5 9.75h15A2.25 2.25 0 0121.75 12v.75m-8.69-6.44l-2.12-2.12a1.5 1.5 0 00-1.061-.44H4.5A2.25 2.25 0 002.25 6v12a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18V9a2.25 2.25 0 00-2.25-2.25h-5.379a1.5 1.5 0 01-1.06-.44z" />
</svg>
Projects
</a>
</li>
<li>
<a href="#" class="text-gray-700 hover:text-indigo-600 hover:bg-gray-50 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
<svg class="h-6 w-6 shrink-0 text-gray-400 group-hover:text-indigo-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75" />
</svg>
Documents
</a>
</li>
<li>
<a href="#" class="text-gray-700 hover:text-indigo-600 hover:bg-gray-50 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
<svg class="h-6 w-6 shrink-0 text-gray-400 group-hover:text-indigo-600" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 8.511c.884.284 1.5 1.128 1.5 2.097v4.286c0 1.136-.847 2.1-1.98 2.193-.34.027-.68.052-1.02.072v3.091l-3-3c-1.354 0-2.694-.055-4.02-.163a2.115 2.115 0 0 1-.825-.242m9.345-8.334a2.126 2.126 0 0 0-.476-.095 48.64 48.64 0 0 0-8.048 0c-1.131.094-1.976 1.057-1.976 2.192v4.286c0 .837.46 1.58 1.155 1.951m9.345-8.334V6.637c0-1.621-1.152-3.026-2.76-3.235A48.455 48.455 0 0 0 11.25 3c-2.115 0-4.198.137-6.24.402-1.608.209-2.76 1.614-2.76 3.235v6.226c0 1.621 1.152 3.026 2.76 3.235.577.075 1.157.14 1.74.194V21l4.155-4.155" />
</svg>
Chats
</a>
</li>
</ul>
</li>
<li>
<div class="text-xs font-semibold leading-6 text-gray-400">Your projects</div>
<ul role="list" class="-mx-2 mt-2 space-y-1">
{% for project in projects %}
<li>
<!-- Current: "bg-gray-50 text-indigo-600", Default: "text-gray-700 hover:text-indigo-600 hover:bg-gray-50" -->
<a href="#" class="text-gray-700 hover:text-indigo-600 hover:bg-gray-50 group flex gap-x-3 rounded-md p-2 text-sm leading-6 font-semibold">
<span class="flex h-6 w-10 shrink-0 items-center justify-center rounded-lg border text-[0.625rem] font-medium bg-white text-gray-400 border-gray-200 group-hover:border-indigo-600 group-hover:text-indigo-600">{{ project.key }}</span>
<span class="truncate">{{ project.name }}</span>
</a>
</li>
{% endfor %}
</ul>
</li>
<li class="-mx-6 mt-auto">
<a href="/profile" class="flex items-center gap-x-4 px-6 py-3 text-sm font-semibold leading-6 text-gray-900 hover:bg-gray-50">
<div class="h-8 w-8 rounded-full bg-gray-50">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-8 h-8">
<path stroke-linecap="round" stroke-linejoin="round" d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</div>
<span class="sr-only">Your profile</span>
<span aria-hidden="true">{{ user.full_name }}</span>
</a>
</li>
</ul>
</nav>
</div>
</div>

View file

@ -4,5 +4,5 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Pique</title>
<link rel="stylesheet" href="/static/main.css">
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>👀</text></svg>"/>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⛰️</text></svg>"/>
</head>

View file

@ -2,115 +2,16 @@
<html lang="en" class="h-full bg-white">
{% include "head.html" %}
<body class="h-full">
<!--
This example requires updating your template:
```
<html class="h-full bg-gray-100">
<body class="h-full">
```
-->
<div class="min-h-full">
<nav class="bg-purple-200">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="flex h-16 items-center justify-between">
<div class="flex items-center">
<div class="flex-shrink-0">
👀
</div>
<div class="hidden md:block">
<div class="ml-10 flex items-baseline space-x-4">
<!-- Current: "bg-purple-700 text-white", Default: "text-gray-700 hover:bg-purple-500 hover:bg-opacity-75" -->
<a href="#" class="bg-purple-700 text-white rounded-md px-3 py-2 text-sm font-medium" aria-current="page">Dashboard</a>
<a href="#" class="text-gray-700 hover:text-white hover:bg-purple-500 hover:bg-opacity-75 rounded-md px-3 py-2 text-sm font-medium">Placeholder</a>
</div>
</div>
</div>
<div class="hidden md:block">
<div class="ml-4 flex items-center md:ml-6">
<!-- Profile dropdown -->
<div class="relative ml-3">
<div>
<button type="button" class="relative flex max-w-xs items-center rounded-full bg-purple-600 text-sm focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-purple-600" id="profile-menu-button" aria-expanded="false" aria-haspopup="true" onClick="toggle_profile_dropdown()">
<span class="absolute -inset-1.5"></span>
<span class="sr-only">Open user menu</span>
<!-- placeholder icon -->
<span class="inline-block h-6 w-6 overflow-hidden rounded-full bg-gray-200">
<svg class="h-full w-full text-gray-400" fill="currentColor" viewBox="0 0 24 24">
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
</span>
</button>
</div>
<div class="hidden absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="user-menu-button" tabindex="-1" id="profile-dropdown">
<div class="ml-3">
<div class="text-base font-medium text-gray-600">{{ user.full_name }}</div>
<div class="text-sm font-medium text-gray-400">{{ user.email }}</div>
</div>
<!-- Active: "bg-gray-100", Not Active: "" -->
<a href="/logout" class="block px-4 py-2 text-sm text-gray-700" role="menuitem" tabindex="-1" id="user-menu-item-2">Sign out</a>
</div>
</div>
</div>
</div>
<div class="-mr-2 flex md:hidden">
<!-- Mobile menu button -->
<button type="button" class="relative inline-flex items-center justify-center rounded-md bg-purple-600 p-2 text-purple-200 hover:bg-purple-500 hover:bg-opacity-75 hover:text-white focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-purple-600" aria-controls="mobile-menu" aria-expanded="false" onClick="toggle_mobile_menu()">
<span class="absolute -inset-0.5"></span>
<span class="sr-only">Open main menu</span>
<!-- Menu open: "hidden", Menu closed: "block" -->
<svg class="block h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" id="mobile-menu-open-button">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
</svg>
<!-- Menu open: "block", Menu closed: "hidden" -->
<svg class="hidden h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true" id="mobile-menu-close-button">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
</div>
<!-- Mobile menu, show/hide based on menu state. -->
<div class="md:hidden hidden" id="mobile-menu">
<div class="space-y-1 px-2 pb-3 pt-2 sm:px-3">
<!-- Current: "bg-purple-700 text-white", Default: "text-white hover:bg-purple-500 hover:bg-opacity-75" -->
<a href="#" class="bg-purple-700 text-white block rounded-md px-3 py-2 text-base font-medium" aria-current="page">Dashboard</a>
<a href="#" class="text-gray-700 hover:text-white hover:bg-purple-500 hover:bg-opacity-75 block rounded-md px-3 py-2 text-base font-medium">Placeholder</a>
</div>
<div class="border-t border-purple-700 pb-3 pt-4">
<div class="flex items-center px-5">
<div class="flex-shrink-0">
<!-- placeholder icon -->
<span class="inline-block h-6 w-6 overflow-hidden rounded-full bg-gray-200">
<svg class="h-full w-full text-gray-400" fill="currentColor" viewBox="0 0 24 24">
<path d="M24 20.993V24H0v-2.996A14.977 14.977 0 0112.004 15c4.904 0 9.26 2.354 11.996 5.993zM16.002 8.999a4 4 0 11-8 0 4 4 0 018 0z" />
</svg>
</span>
</div>
<div class="ml-3">
<div class="text-base font-medium text-gray-600">{{ user.full_name }}</div>
<div class="text-sm font-medium text-gray-400">{{ user.email }}</div>
</div>
</div>
<div class="mt-3 space-y-1 px-2">
<a href="/logout" class="block rounded-md px-3 py-2 text-base font-medium text-gray-700 hover:bg-purple-500 hover:bg-opacity-75">Sign out</a>
</div>
</div>
</div>
</nav>
<main>
<div class="mx-auto max-w-7xl py-6 sm:px-6 lg:px-8">
Hi there!
{% include "components/sidebar.html" %}
<main class="py-10 pl-72">
<div class="px-8">
Main content.
</div>
</main>
</div>
<script type="text/javascript" src="/static/main.js"></script>
</body>