Forráskód Böngészése

Create the style/layout for the dashboard

Lee Morgan 1 hónapja
szülő
commit
20ea7ab27f
4 módosított fájl, 318 hozzáadás és 15 törlés
  1. 23 14
      src/controllers/ws_connect.rs
  2. 263 0
      ui/dashboard/index.css
  3. 1 0
      ui/index.css
  4. 31 1
      ui/index.html

+ 23 - 14
src/controllers/ws_connect.rs

@@ -15,20 +15,9 @@ pub async fn route(
     users: web::Data<Mutex<Vec<User>>>,
     registry: web::Data<ClientRegistry>
 ) -> HttpResponse {
-    let cookie = match req.cookie("user") {
-        Some(c) => c,
-        None => { return HttpResponse::Unauthorized().json(json!({"msg": "Unauthorized"})) }
-    };
-    let user_id = match Uuid::parse_str(&cookie.value()) {
-        Ok(id) => id,
-        Err(_) => return HttpResponse::InternalServerError().finish()
-    };
-    let user = match users.lock() {
-        Ok(us) => match us.iter().find(|u| u.id == user_id).cloned() {
-            Some(a) => a,
-            None => { return HttpResponse::Unauthorized().json(json!({"msg": "Unauthorized"})) }
-        },
-        Err(_) => { return HttpResponse::InternalServerError().finish()}
+    let user = match get_user(&req, users) {
+        Ok(u) => u,
+        Err(e) => { return e; }
     };
 
     let (response, mut session, mut msg_stream) =  match actix_ws::handle(&req, body) {
@@ -63,3 +52,23 @@ pub async fn route(
 
     response
 }
+
+fn get_user(req: &HttpRequest, users: web::Data<Mutex<Vec<User>>>) -> Result<User, HttpResponse> {
+        let cookie = match req.cookie("user") {
+            Some(c) => c,
+        None => { return Err(HttpResponse::Unauthorized().json(json!({"msg": "Unauthorized"}))) }
+    };
+
+    let user_id = match Uuid::parse_str(&cookie.value()) {
+        Ok(id) => id,
+        Err(_) => { return Err(HttpResponse::InternalServerError().finish()) }
+    };
+
+    match users.lock() {
+        Ok(us) => match us.iter().find(|u| u.id == user_id).cloned() {
+            Some(a) => Ok(a),
+            None => { return Err(HttpResponse::Unauthorized().json(json!({"msg": "Unauthorized"}))) }
+        },
+        Err(_) => { return Err(HttpResponse::InternalServerError().finish()) }
+    }
+}

+ 263 - 0
ui/dashboard/index.css

@@ -0,0 +1,263 @@
+#dashboardPage{
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    width: 100%;
+    background: var(--bg-base);
+    position: relative;
+    overflow: hidden;
+
+    .dashboard-menu-toggle-input{
+        position: absolute;
+        opacity: 0;
+        pointer-events: none;
+    }
+
+    .dashboard-topbar{
+        display: flex;
+        align-items: center;
+        gap: 14px;
+        flex-shrink: 0;
+        height: 56px;
+        padding: 0 16px;
+        background: var(--bg-surface);
+        border-bottom: 1px solid var(--border-subtle);
+        z-index: 20;
+    }
+
+    .dashboard-menu-toggle{
+        display: flex;
+        flex-direction: column;
+        justify-content: center;
+        gap: 5px;
+        width: 40px;
+        height: 40px;
+        padding: 10px 8px;
+        cursor: pointer;
+        border: 1px solid transparent;
+        transition: background 0.15s ease, border-color 0.15s ease;
+    }
+
+    .dashboard-menu-toggle:hover{
+        background: var(--bg-surface-raised);
+        border-color: var(--border-subtle);
+    }
+
+    .dashboard-menu-toggle span{
+        display: block;
+        height: 2px;
+        width: 100%;
+        background: var(--text-primary);
+        transition: transform 0.2s ease, opacity 0.2s ease;
+    }
+
+    .dashboard-topbar-title{
+        font-size: 16px;
+        font-weight: 700;
+        letter-spacing: -0.02em;
+        color: var(--text-primary);
+    }
+
+    .dashboard-menu-backdrop{
+        position: absolute;
+        inset: 0;
+        background: rgba(0, 0, 0, 0.55);
+        opacity: 0;
+        visibility: hidden;
+        pointer-events: none;
+        transition: opacity 0.2s ease, visibility 0.2s ease;
+        z-index: 30;
+    }
+
+    .dashboard-menu{
+        position: absolute;
+        top: 0;
+        left: 0;
+        bottom: 0;
+        width: min(280px, 86vw);
+        display: flex;
+        flex-direction: column;
+        background: var(--bg-surface);
+        border-right: 1px solid var(--border-subtle);
+        transform: translateX(-100%);
+        transition: transform 0.22s ease;
+        z-index: 40;
+    }
+
+    .dashboard-menu-header{
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        gap: 12px;
+        height: 56px;
+        padding: 0 12px 0 20px;
+        border-bottom: 1px solid var(--border-subtle);
+        flex-shrink: 0;
+    }
+
+    .dashboard-menu-brand{
+        position: relative;
+        font-size: 15px;
+        font-weight: 700;
+        letter-spacing: -0.02em;
+        color: var(--text-primary);
+        padding-left: 14px;
+    }
+
+    .dashboard-menu-brand::before{
+        content: "";
+        position: absolute;
+        left: 0;
+        top: 2px;
+        bottom: 2px;
+        width: 3px;
+        background: var(--accent);
+    }
+
+    .dashboard-menu-close{
+        position: relative;
+        width: 40px;
+        height: 40px;
+        cursor: pointer;
+        border: 1px solid transparent;
+        transition: background 0.15s ease, border-color 0.15s ease;
+    }
+
+    .dashboard-menu-close:hover{
+        background: var(--bg-surface-raised);
+        border-color: var(--border-subtle);
+    }
+
+    .dashboard-menu-close::before,
+    .dashboard-menu-close::after{
+        content: "";
+        position: absolute;
+        top: 50%;
+        left: 50%;
+        width: 16px;
+        height: 2px;
+        background: var(--text-secondary);
+    }
+
+    .dashboard-menu-close::before{
+        transform: translate(-50%, -50%) rotate(45deg);
+    }
+
+    .dashboard-menu-close::after{
+        transform: translate(-50%, -50%) rotate(-45deg);
+    }
+
+    .dashboard-menu-list{
+        list-style: none;
+        display: flex;
+        flex-direction: column;
+        gap: 8px;
+        padding: 16px;
+        margin-top: auto;
+    }
+
+    .dashboard-menu-logout{
+        width: 100%;
+        font-size: 13px;
+        font-weight: 700;
+        letter-spacing: 0.08em;
+        text-transform: uppercase;
+        color: var(--text-primary);
+        background: transparent;
+        border: 1px solid var(--border-strong);
+        border-radius: 0;
+        padding: 14px 16px;
+        cursor: pointer;
+        transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, transform 0.05s ease;
+    }
+
+    .dashboard-menu-logout:hover{
+        color: #fff;
+        background: var(--danger);
+        border-color: var(--danger);
+    }
+
+    .dashboard-menu-logout:active{
+        transform: translateY(1px);
+    }
+
+    .dashboard-main{
+        flex: 1;
+        min-height: 0;
+        overflow: auto;
+        padding: 20px 16px;
+    }
+
+    /* Open state (mobile drawer) */
+    .dashboard-menu-toggle-input:checked ~ .dashboard-menu-backdrop{
+        opacity: 1;
+        visibility: visible;
+        pointer-events: auto;
+    }
+
+    .dashboard-menu-toggle-input:checked ~ .dashboard-menu{
+        transform: translateX(0);
+    }
+
+    .dashboard-menu-toggle-input:checked ~ .dashboard-topbar .dashboard-menu-toggle span:nth-child(1){
+        transform: translateY(7px) rotate(45deg);
+    }
+
+    .dashboard-menu-toggle-input:checked ~ .dashboard-topbar .dashboard-menu-toggle span:nth-child(2){
+        opacity: 0;
+    }
+
+    .dashboard-menu-toggle-input:checked ~ .dashboard-topbar .dashboard-menu-toggle span:nth-child(3){
+        transform: translateY(-7px) rotate(-45deg);
+    }
+}
+
+/* Persistent sidebar on larger screens — works with inline display:flex from openPage */
+@media (min-width: 768px){
+    #dashboardPage{
+        padding-left: 260px;
+
+        .dashboard-topbar{
+            padding-left: 24px;
+        }
+
+        .dashboard-menu-toggle,
+        .dashboard-menu-backdrop,
+        .dashboard-menu-close{
+            display: none;
+        }
+
+        .dashboard-menu{
+            width: 260px;
+            transform: none;
+        }
+
+        .dashboard-menu-header{
+            justify-content: flex-start;
+            padding-left: 24px;
+            padding-right: 16px;
+        }
+
+        .dashboard-menu-list{
+            padding: 20px 16px;
+        }
+
+        .dashboard-main{
+            padding: 28px 24px;
+        }
+    }
+}
+
+@media (min-width: 1024px){
+    #dashboardPage{
+        padding-left: 280px;
+
+        .dashboard-menu{
+            width: 280px;
+        }
+
+        .dashboard-main{
+            padding: 32px 32px;
+        }
+    }
+}

+ 1 - 0
ui/index.css

@@ -1,5 +1,6 @@
 @import "./login/index.css";
 @import "./adminCreate/index.css";
+@import "./dashboard/index.css";
 
 /*
 Background layers

+ 31 - 1
ui/index.html

@@ -29,7 +29,37 @@
     </div>
 
     <div id="dashboardPage" class="page" style="display: none">
-        <h1>Dashboard</h1>
+        <input type="checkbox" id="dashboardMenuOpen" class="dashboard-menu-toggle-input" hidden>
+
+        <header class="dashboard-topbar">
+            <label for="dashboardMenuOpen" class="dashboard-menu-toggle" aria-label="Open menu">
+                <span></span>
+                <span></span>
+                <span></span>
+            </label>
+            <h1 class="dashboard-topbar-title">Dashboard</h1>
+        </header>
+
+        <label for="dashboardMenuOpen" class="dashboard-menu-backdrop" aria-hidden="true"></label>
+
+        <nav class="dashboard-menu" aria-label="Dashboard menu">
+            <div class="dashboard-menu-header">
+                <span class="dashboard-menu-brand">Home Assistant</span>
+                <label for="dashboardMenuOpen" class="dashboard-menu-close" aria-label="Close menu"></label>
+            </div>
+            <ul class="dashboard-menu-list">
+                <li>
+                    <button
+                        type="button"
+                        id="dashboardLogout"
+                        class="dashboard-menu-logout"
+                        onclick="document.getElementById('dashboardMenuOpen').checked = false; localStorage.removeItem('loggedIn'); openPage('login');"
+                    >Log Out</button>
+                </li>
+            </ul>
+        </nav>
+
+        <main></main>
     </div>
 
     <div id="adminCreatePage" class="page" style="display: none">