updated header so all styling is now in the designated themes file
This commit is contained in:
+32
-37
@@ -19,41 +19,7 @@ import GlobalMap from "./pages/maps/GlobalMap.jsx";
|
||||
import Admin from "./pages/admin/Admin.jsx";
|
||||
import MyDashboard from "./pages/home/tabs/MyDashboard.jsx";
|
||||
|
||||
function AppFoundation() {
|
||||
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"
|
||||
>
|
||||
<GridItem rowSpan={2} colSpan={1}>
|
||||
<Sidebar />
|
||||
</GridItem>
|
||||
<GridItem rowSpan={1} colSpan={1}>
|
||||
<Header />
|
||||
</GridItem>
|
||||
<GridItem colSpan={1} width="100%" overflow="auto">
|
||||
<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>
|
||||
);
|
||||
|
||||
function App() {
|
||||
export default function App() {
|
||||
// 1. Pull the initialization function from Zustand
|
||||
const checkAuth = useAuthStore((state) => state.checkAuth);
|
||||
|
||||
@@ -67,7 +33,7 @@ function App() {
|
||||
{/* --- Public Routes --- */}
|
||||
<Route path="/login" element={<Login/>}/>
|
||||
|
||||
{/* --- SECURE ROUTES --- */}
|
||||
{/* --- Secure Routes --- */}
|
||||
<Route element={<ProtectedRoute/>}>
|
||||
|
||||
{/* --- Non-Public Routes (Now completely locked down) --- */}
|
||||
@@ -95,4 +61,33 @@ function App() {
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
// The fundamental layout of the webpage
|
||||
function AppFoundation() {
|
||||
const recipe = useRecipe({key: "dashboard"});
|
||||
const styles = recipe();
|
||||
const dialog = useDialog();
|
||||
|
||||
return (
|
||||
<>
|
||||
<DialogPopup dialog={dialog}/>
|
||||
<Grid css={styles} templateRows="1fr 11fr" templateColumns="auto 1fr">
|
||||
<GridItem rowSpan={2} colSpan={1}>
|
||||
<Sidebar/>
|
||||
</GridItem>
|
||||
<GridItem rowSpan={1} colSpan={1}>
|
||||
<Header/>
|
||||
</GridItem>
|
||||
<GridItem colSpan={1}>
|
||||
<Outlet/>
|
||||
</GridItem>
|
||||
</Grid>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
// 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>
|
||||
);
|
||||
@@ -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,7 +8,7 @@ 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 recipe = useSlotRecipe({key: "header"});
|
||||
const styles = recipe();
|
||||
|
||||
const {user} = useAuthStore();
|
||||
@@ -20,11 +19,14 @@ export default function Header() {
|
||||
const displayEmail = user?.email || "LDAP User";
|
||||
|
||||
return (
|
||||
<Flex css={styles} >
|
||||
<SearchBar placeholder="Enter a volcano to search" size="sm" width="410px" />
|
||||
<Flex css={styles.root}>
|
||||
<Flex css={styles.search_bar}>
|
||||
<SearchBar placeholder="Enter a volcano to search"/>
|
||||
</Flex>
|
||||
|
||||
{/* User Profile Section */}
|
||||
<Flex align="center" gap={3} pr={6}>
|
||||
<Text fontWeight="bold" textStyle="md" >{displayName}</Text>
|
||||
<Flex css={styles.user_info}>
|
||||
<Text>{displayName}</Text>
|
||||
<UserAvatarMenu
|
||||
name={avatarname}
|
||||
// avatar={user.avatar}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
+39
-4
@@ -1,8 +1,10 @@
|
||||
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: {
|
||||
root: {
|
||||
bg: "{transparent}",
|
||||
color: "text",
|
||||
width: "100%",
|
||||
@@ -11,7 +13,29 @@ const headerRecipe = defineRecipe({
|
||||
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);
|
||||
Reference in New Issue
Block a user