updated header so all styling is now in the designated themes file

This commit is contained in:
2026-09-03 14:07:39 -07:00
parent cde2c4660b
commit 4aa8eb7ff0
5 changed files with 144 additions and 115 deletions
+58 -63
View File
@@ -1,7 +1,7 @@
import 'leaflet/dist/leaflet.css';
import { useEffect } from 'react';
import { Routes, Route, Navigate, Outlet } from 'react-router-dom';
import { useRecipe, Grid, GridItem, useDialog, Flex, Heading } from "@chakra-ui/react";
import {useEffect} from 'react';
import {Routes, Route, Navigate, Outlet} from 'react-router-dom';
import {useRecipe, Grid, GridItem, useDialog, Flex, Heading} from "@chakra-ui/react";
// --- NEW AUTH IMPORTS ---
import useAuthStore from './store/authStore';
@@ -19,80 +19,75 @@ import GlobalMap from "./pages/maps/GlobalMap.jsx";
import Admin from "./pages/admin/Admin.jsx";
import MyDashboard from "./pages/home/tabs/MyDashboard.jsx";
export default function App() {
// 1. Pull the initialization function from Zustand
const checkAuth = useAuthStore((state) => state.checkAuth);
// 2. Ping Django to verify the HttpOnly cookie the moment the app boots
useEffect(() => {
checkAuth();
}, [checkAuth]);
return (
<Routes>
{/* --- Public Routes --- */}
<Route path="/login" element={<Login/>}/>
{/* --- Secure Routes --- */}
<Route element={<ProtectedRoute/>}>
{/* --- Non-Public Routes (Now completely locked down) --- */}
<Route element={<AppFoundation/>}>
<Route path="/home" element={<Home/>}>
<Route index element={<MyDashboard/>}/>
<Route path="gas" element={<Placeholder name="Gas"/>}/>
<Route path="seismic" element={<Placeholder name="Seismic"/>}/>
<Route path="remote" element={<Placeholder name="Remote Sensing"/>}/>
<Route path="daily" element={<Placeholder name="Daily Activity"/>}/>
</Route>
<Route path="/global-map" element={<GlobalMap/>}/>
<Route path="/regional-map" element={<RegionalMap/>}/>
<Route path="/volcano" element={<Volcano/>}/>
<Route path="/admin" element={<Admin/>}/>
</Route>
</Route>
{/* --- Redirect Routes --- */}
<Route path="/" element={<Navigate to="/home" replace/>}/>
</Routes>
);
}
// The fundamental layout of the webpage
function AppFoundation() {
const recipe = useRecipe({ key: "dashboard" });
const recipe = useRecipe({key: "dashboard"});
const styles = recipe();
const dialog = useDialog();
return (
<>
<DialogPopup dialog={dialog} />
<Grid css={styles}
templateRows="1fr 11fr"
templateColumns="auto 1fr"
height="100vh"
width="100vw"
>
<DialogPopup dialog={dialog}/>
<Grid css={styles} templateRows="1fr 11fr" templateColumns="auto 1fr">
<GridItem rowSpan={2} colSpan={1}>
<Sidebar />
<Sidebar/>
</GridItem>
<GridItem rowSpan={1} colSpan={1}>
<Header />
<Header/>
</GridItem>
<GridItem colSpan={1} width="100%" overflow="auto">
<Outlet />
<GridItem colSpan={1}>
<Outlet/>
</GridItem>
</Grid>
</>
)
}
const Placeholder = ({ name }) => (
<Flex width="100%" height="100%" align="center" justify="center">
<Heading color="gray.500" _dark={{ color: "gray.200" }} size="lg">{name} Dashboard - Coming Soon</Heading>
</Flex>
// A dummy page that acts as a placeholder for pages that aren't developed yet but are navigatable to
const Placeholder = ({name}) => (
<Flex width="100%" height="100%" align="center" justify="center">
<Heading color="gray.500" _dark={{color: "gray.200"}} size="lg">{name} Dashboard - Coming Soon</Heading>
</Flex>
);
function App() {
// 1. Pull the initialization function from Zustand
const checkAuth = useAuthStore((state) => state.checkAuth);
// 2. Ping Django to verify the HttpOnly cookie the moment the app boots
useEffect(() => {
checkAuth();
}, [checkAuth]);
return (
<Routes>
{/* --- Public Routes --- */}
<Route path="/login" element={<Login />} />
{/* --- SECURE ROUTES --- */}
<Route element={<ProtectedRoute />}>
{/* --- Non-Public Routes (Now completely locked down) --- */}
<Route element={<AppFoundation />}>
<Route path="/home" element={<Home />}>
<Route index element={<MyDashboard />} />
<Route path="gas" element={<Placeholder name="Gas" />} />
<Route path="seismic" element={<Placeholder name="Seismic" />} />
<Route path="remote" element={<Placeholder name="Remote Sensing" />} />
<Route path="daily" element={<Placeholder name="Daily Activity" />} />
</Route>
<Route path="/global-map" element={<GlobalMap />} />
<Route path="/regional-map" element={<RegionalMap />} />
<Route path="/volcano" element={<Volcano />} />
<Route path="/admin" element={<Admin />} />
</Route>
</Route>
{/* --- Redirect Routes --- */}
<Route path="/" element={<Navigate to="/home" replace />} />
</Routes>
);
}
export default App;
+24 -22
View File
@@ -1,6 +1,5 @@
import { useRecipe, Button, Stack, Text, Flex } from "@chakra-ui/react"
import {useSlotRecipe, Text, Flex} from "@chakra-ui/react"
import UserAvatarMenu from "./UserAvatarMenu.jsx";
import { NavLink } from "react-router-dom";
import useAuthStore from "@/store/authStore";
import {SearchBar} from "@/components/dashboard/SearchBar.jsx";
@@ -9,27 +8,30 @@ import {SearchBar} from "@/components/dashboard/SearchBar.jsx";
// Accept a 'user' prop now, fallback to placeholder if empty
export default function Header() {
const recipe = useRecipe({ key: "header" });
const styles = recipe();
const recipe = useSlotRecipe({key: "header"});
const styles = recipe();
const { user } = useAuthStore();
console.log(user);
const displayName = user ? (user.firstName || user.username) : "Loading...";
const avatarname = user ? (user.fullName || user.username) : "Loading...";
const {user} = useAuthStore();
console.log(user);
const displayName = user ? (user.firstName || user.username) : "Loading...";
const avatarname = user ? (user.fullName || user.username) : "Loading...";
const displayEmail = user?.email || "LDAP User";
const displayEmail = user?.email || "LDAP User";
return (
<Flex css={styles} >
<SearchBar placeholder="Enter a volcano to search" size="sm" width="410px" />
{/* User Profile Section */}
<Flex align="center" gap={3} pr={6}>
<Text fontWeight="bold" textStyle="md" >{displayName}</Text>
<UserAvatarMenu
name={avatarname}
// avatar={user.avatar}
/>
</Flex>
</Flex>
);
return (
<Flex css={styles.root}>
<Flex css={styles.search_bar}>
<SearchBar placeholder="Enter a volcano to search"/>
</Flex>
{/* User Profile Section */}
<Flex css={styles.user_info}>
<Text>{displayName}</Text>
<UserAvatarMenu
name={avatarname}
// avatar={user.avatar}
/>
</Flex>
</Flex>
);
}
+1 -2
View File
@@ -49,7 +49,6 @@ export default function Home() {
{/* The Settings button is now inline with the tabs and only renders on the personal dashboard */}
{isPersonalDashboard && <SettingsButton/>}
</Flex>
{/* EDIT MODE BUTTONS & CONTROLS */}
@@ -57,9 +56,9 @@ export default function Home() {
<HStack width="258px" justifyContent="flex-end">
{/* Future global controls go here */}
</HStack>
</Flex>
{/* TABS CONTENT */}
<Flex flex="1" overflow="auto">
<Outlet/>
</Flex>
+12 -14
View File
@@ -13,8 +13,8 @@ import {WIDGET_REGISTRY} from '@/constants/widgetRegistry.jsx';
// React-Grid-Layout requires this wrapper to automatically calculate screen width
const ResponsiveGridLayout = WidthProvider(Responsive);
export default function MyDashboard() {
export default function MyDashboard() {
// ☁️ Database Connection
const {user} = useAuthStore();
@@ -28,25 +28,23 @@ export default function MyDashboard() {
// 🔒 Safety lock to prevent infinite re-renders
const hasHydrated = useRef(false);
// 1. Pull the new global slot recipe
// 1. Pull the global slot recipes
const recipe = useSlotRecipe({key: "glassPanel"});
const styles = recipe({ layout: "widget" });
const popoverStyles = recipe({ layout: "popover" });
// 2. Populate widgets
useEffect(() => {
if (user?.preferences?.dashboard_layout && !hasHydrated.current) {
const savedLayout = user.preferences.dashboard_layout;
// Safety check: Only inject if it's actually an array of widgets.
// (Prevents a crash if MariaDB returns a default empty object {})
if (Array.isArray(savedLayout)) {
setWidgets(savedLayout);
}
if (Array.isArray(savedLayout)) setWidgets(savedLayout);
hasHydrated.current = true;
}
}, [user, setWidgets]);
// 🛠️ Format the data for RGL
const rglLayout = widgets.map(w => ({
i: w.id,
@@ -54,10 +52,10 @@ export default function MyDashboard() {
y: w.y,
w: w.w,
h: w.h,
isResizable: false // 🚫 The magic lock! No resizing allowed globally.
isResizable: false // The magic lock! No resizing allowed globally.
}));
// 3. THE COMPONENT MAPPER
// 3. The component mapper
const renderWidget = (widget) => {
const registryEntry = WIDGET_REGISTRY[widget.type];
@@ -74,7 +72,7 @@ export default function MyDashboard() {
{registryEntry.name}
</Text>
{/* THE SETTINGS ELLIPSIS */}
{/* WIDGET SETTINGS BUTTON (ELLIPSES) */}
{SettingsUI ? (
<Popover.Root positioning={{placement: "bottom-end"}}>
<Popover.Trigger asChild>
@@ -136,20 +134,20 @@ export default function MyDashboard() {
onLayoutChange={(newLayout) => updateLayoutPositions(newLayout)} // Fires when user drops a widget
compactType="vertical" // Auto-packs widgets to the top
>
{/* 3. 🎨 Paint the Widgets */}
{/* 🎨 Paint the Widgets */}
{widgets.map(widget => (
<Box
key={widget.id}
css={styles.root}
// 3. Grid-specific state overrides remain inline
// Grid-specific state overrides remain inline
boxShadow={isEditing ? "outline" : styles.root.boxShadow}
borderStyle={isEditing ? "dashed" : "solid"}
borderWidth={isEditing ? "2px" : "1px"}
borderColor={isEditing ? "orange.300" : styles.root.borderColor}
cursor={isEditing ? "grab" : "default"}
position="relative" // 3. REQUIRED so the absolute button stays inside this box
position="relative" // REQUIRED so the absolute button stays inside this box
>
{/* 4. THE DELETE BUTTON */}
{/* THE DELETE BUTTON */}
{isEditing && (
<IconButton
aria-label="Remove widget"
@@ -172,7 +170,7 @@ export default function MyDashboard() {
</IconButton>
)}
{/* 4. SUSPENSE BOUNDARY wrapping the render function */}
{/* SUSPENSE BOUNDARY wrapping the render function */}
<Suspense fallback={
<Flex css={styles.body} align="center" justify="center">
<Spinner size="md" color="blue.400" thickness="3px"/>
+48 -13
View File
@@ -1,17 +1,41 @@
import {defineSlotRecipe, createSystem, defaultConfig, defineConfig, defineRecipe} from "@chakra-ui/react";
//—————RECIPES—————//
const headerRecipe = defineRecipe({
// CONSOLIDATED ✔
const headerRecipe = defineSlotRecipe({
slots: ["root", "search_bar", "user_info"],
base: {
bg: "{transparent}",
color: "text",
width: "100%",
height: "100%",
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
}
root: {
bg: "{transparent}",
color: "text",
width: "100%",
height: "100%",
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
pl: 3,
},
search_bar: {
width: "100%",
// input element in search bar component
"& input": {
width: "410px",
}
},
user_info: {
flexDirection: "row",
justifyContent: "flex-end",
alignItems: "center",
gap: 3,
pr: 6,
// The Text component
"& p": {
fontWeight: "bold",
fontSize:"16px" // equivalent to chakra 'md'
}
}
},
});
// CONSOLIDATED ✔
@@ -208,9 +232,18 @@ const sidebarRecipe = defineSlotRecipe({
}
});
// CONSOLIDATED ✔
const dashboardRecipe = defineRecipe({
base: {
bg: "{MainBackground}"
height: "100vh",
width: "100vw",
bg: "{MainBackground}",
// selects the third div in the grid layout (the canvas)
"& > div:nth-of-type(3)": {
width: "100%",
overflow: "auto",
}
}
});
@@ -484,12 +517,12 @@ const config= defineConfig({
},
},
recipes: {
header: headerRecipe,
dashboard: dashboardRecipe,
canvas: canvasRecipe,
glassTab: glassTabRecipe
},
slotRecipes: {
header: headerRecipe,
sidebar: sidebarRecipe,
// widget: widgetRecipe,
glassPanel: glassPanelRecipe,
@@ -497,4 +530,6 @@ const config= defineConfig({
},
});
// Define semantic tokens and export recipes
export const system = createSystem(defaultConfig, config);