revamp template

This commit is contained in:
fro1991
2025-02-01 16:44:51 +07:00
parent c432df568a
commit 276a289580
1212 changed files with 112762 additions and 0 deletions

View File

@ -0,0 +1,75 @@
import { Link, Outlet } from 'react-router-dom';
import { Fragment, useState, useEffect } from 'react';
import { toAbsoluteUrl } from '@/utils';
import useBodyClasses from '@/hooks/useBodyClasses';
import { AuthBrandedLayoutProvider } from './AuthBrandedLayoutProvider';
const Layout = () => {
const backgroundImages = [
'/media/login-image/img_01.jpg',
'/media/login-image/img_02.jpg',
'/media/login-image/img_03.jpg'
];
const [currentImageIndex, setCurrentImageIndex] = useState(0);
const [nextImageIndex, setNextImageIndex] = useState(1);
const [isTransitioning, setIsTransitioning] = useState(false);
useBodyClasses('dark:bg-coal-500');
useEffect(() => {
const interval = setInterval(() => {
const nextIndex = (currentImageIndex + 1) % backgroundImages.length;
setNextImageIndex(nextIndex);
setIsTransitioning(true);
setTimeout(() => {
setCurrentImageIndex(nextIndex);
setIsTransitioning(false);
}, 3000);
}, 5000);
return () => clearInterval(interval);
}, [currentImageIndex, backgroundImages.length]);
return (
<Fragment>
<div className="flex h-full w-full">
<div
className="order-1 lg:order-1 hidden md:block xl:bg-cover lg:w-8/12 bg-no-repeat branded-bg"
style={{ borderRadius: 0 }}
>
<div className="relative h-screen w-full overflow-hidden">
<img
src={`${toAbsoluteUrl(backgroundImages[currentImageIndex])}`}
className={`absolute top-0 left-0 h-full w-full transition-opacity duration-3000 ${
isTransitioning ? 'opacity-0' : 'opacity-100'
}`}
style={{ objectFit: 'cover' }}
alt={`Background ${currentImageIndex}`}
/>
{/* Next Image */}
<img
src={`${toAbsoluteUrl(backgroundImages[nextImageIndex])}`}
className={`absolute top-0 left-0 h-full w-full transition-opacity duration-3000 ${
isTransitioning ? 'opacity-100' : 'opacity-0'
}`}
style={{ objectFit: 'cover' }}
alt={`Background ${nextImageIndex}`}
/>
</div>
</div>
<div className="flex justify-center items-center lg:w-4/12 w-full order-2 lg:order-2 bg-white">
<Outlet />
</div>
</div>
</Fragment>
);
};
const AuthBrandedLayout = () => (
<AuthBrandedLayoutProvider>
<Layout />
</AuthBrandedLayoutProvider>
);
export { AuthBrandedLayout };

View File

@ -0,0 +1,12 @@
import { type ILayoutConfig } from '@/providers/LayoutProvider';
// Defining the configuration for the branded authentication layout
const authLayoutBrandedConfig: ILayoutConfig = {
// Setting the layout name to 'auth-branded'
name: 'auth-branded',
// Currently no additional options defined, but this object can be extended in the future
options: {}
};
export { authLayoutBrandedConfig };

View File

@ -0,0 +1,52 @@
import { createContext, type PropsWithChildren, useContext, useEffect, useState } from 'react';
import { deepMerge } from '@/utils';
import { ILayoutConfig, useLayout } from '@/providers';
import { authLayoutBrandedConfig } from './AuthBrandedLayoutConfig';
// Defining the interface for AuthLayoutProvider's props, which includes a layout of type ILayoutConfig
interface AuthLayoutProviderProps {
layout: ILayoutConfig;
}
// Initial layout properties for the AuthBrandedLayoutProvider, using authLayoutBrandedConfig as the default layout
const initalLayoutProps: AuthLayoutProviderProps = {
layout: authLayoutBrandedConfig
};
// Creating a context for the AuthBrandedLayout with the initial layout properties
const LayoutContext = createContext<AuthLayoutProviderProps>(initalLayoutProps);
// Custom hook to access the AuthBrandedLayout context, allowing other components to use the layout data
const useAuthBrandedLayout = () => useContext(LayoutContext);
// AuthBrandedLayoutProvider component that wraps its children with the layout context
const AuthBrandedLayoutProvider = ({ children }: PropsWithChildren) => {
const { getLayout, setCurrentLayout } = useLayout(); // Access layout-related functions
// Function to merge the current layout with the branded auth layout configuration
const getLayoutConfig = () => {
return deepMerge(authLayoutBrandedConfig, getLayout(authLayoutBrandedConfig.name));
};
// Setting the layout state with the merged layout configuration
const [layout] = useState(getLayoutConfig);
// Effect hook to set the current layout whenever the layout state changes
useEffect(() => {
setCurrentLayout(layout);
}, []);
// Providing the layout context to all child components wrapped by AuthBrandedLayoutProvider
return (
<LayoutContext.Provider
value={{
layout
}}
>
{children}
</LayoutContext.Provider>
);
};
// eslint-disable-next-line react-refresh/only-export-components
export { AuthBrandedLayoutProvider, useAuthBrandedLayout };

View File

@ -0,0 +1 @@
export * from './AuthBrandedLayout';

View File

@ -0,0 +1,24 @@
import useBodyClasses from '@/hooks/useBodyClasses';
import { Demo2LayoutProvider, Main } from './';
const Demo2Layout = () => {
// Using the custom hook to set multiple CSS variables and class properties
useBodyClasses(`
[--tw-page-bg:var(--tw-light)]
[--tw-page-bg-dark:var(--tw-coal-500)]
[--tw-header-height-default:100px]
[[data-sticky-header=on]&]:[--tw-header-height:60px]
[--tw-header-height:--tw-header-height-default]
bg-[--tw-page-bg]
dark:bg-[--tw-page-bg-dark]
`);
return (
// Providing layout context and rendering the main content
<Demo2LayoutProvider>
<Main />
</Demo2LayoutProvider>
);
};
export { Demo2Layout };

View File

@ -0,0 +1,13 @@
import { type ILayoutConfig } from '@/providers';
// Defining the layout configuration specific to Demo2 layout
const Demo2LayoutConfig: ILayoutConfig = {
name: 'demo2-layout', // Unique name identifier for this layout
options: {
header: {
stickyOffset: 200 // Offset value (in pixels) that determines when the header becomes sticky on scroll
}
}
};
export { Demo2LayoutConfig };

View File

@ -0,0 +1,75 @@
import { createContext, type PropsWithChildren, useContext, useEffect, useState } from 'react';
import { MENU_SIDEBAR } from '@/config';
import { useScrollPosition } from '@/hooks/useScrollPosition';
import { useMenus } from '@/providers';
import { ILayoutConfig, useLayout } from '@/providers';
import { deepMerge } from '@/utils';
import { Demo2LayoutConfig } from './';
// Interface defining the properties of the layout provider context
export interface IDemo2LayoutProviderProps {
layout: ILayoutConfig; // The layout configuration object
headerSticky: boolean; // Whether the header should stick to the top on scroll
mobileSidebarOpen: boolean; // Whether the mobile sidebar is open
setMobileSidebarOpen: (open: boolean) => void; // Function to toggle the mobile sidebar
}
// Initial layout provider properties, using Demo2 layout configuration as the default
const initalLayoutProps: IDemo2LayoutProviderProps = {
layout: Demo2LayoutConfig, // Default layout configuration
headerSticky: false, // Header is not sticky by default
mobileSidebarOpen: false, // Mobile sidebar is closed by default
setMobileSidebarOpen: (open: boolean) => {
console.log(`${open}`);
}
};
// Create a context to manage the layout-related state and logic for Demo2 layout
const Demo2LayoutContext = createContext<IDemo2LayoutProviderProps>(initalLayoutProps);
// Custom hook to access the layout context in other components
const useDemo2Layout = () => useContext(Demo2LayoutContext);
// Provider component that sets up the layout state and context for Demo2 layout
const Demo2LayoutProvider = ({ children }: PropsWithChildren) => {
const { setMenuConfig } = useMenus(); // Hook to manage menu configurations
const { getLayout, setCurrentLayout } = useLayout(); // Hook to get and set layout configuration
// Merge the Demo2 layout configuration with the current layout configuration fetched via getLayout
const layoutConfig = deepMerge(Demo2LayoutConfig, getLayout(Demo2LayoutConfig.name));
// Set the initial state for layout and mobile sidebar
const [layout] = useState(layoutConfig); // Layout configuration is stored in state
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false); // Manage state for mobile sidebar
// Get the current scroll position using a custom hook
const scrollPosition = useScrollPosition();
// Calculate whether the header should be sticky based on the scroll position and the layout's sticky offset
const headerSticky: boolean = scrollPosition > layout.options.header.stickyOffset;
// Set the menu configuration for the primary menu using the provided MENU_SIDEBAR configuration
setMenuConfig('primary', MENU_SIDEBAR);
// When the layout state changes, set the current layout configuration in the layout provider
useEffect(() => {
setCurrentLayout(layout); // Update the current layout in the global layout state
}, [layout, setCurrentLayout]); // Re-run this effect if layout or setCurrentLayout changes
// Provide the layout state, sticky header state, and sidebar state to children components via context
return (
<Demo2LayoutContext.Provider
value={{
layout, // The current layout configuration
headerSticky, // Whether the header should be sticky based on the scroll position
mobileSidebarOpen, // Whether the mobile sidebar is currently open
setMobileSidebarOpen // Function to toggle the mobile sidebar state
}}
>
{children} {/* Render child components that consume this context */}
</Demo2LayoutContext.Provider>
);
};
// eslint-disable-next-line react-refresh/only-export-components
export { Demo2LayoutProvider, useDemo2Layout };

View File

@ -0,0 +1,22 @@
import { Container } from '@/components/container';
const Footer = () => {
const currentYear = new Date().getFullYear();
return (
<footer className="footer">
<Container>
<div className="flex flex-col md:flex-row justify-center md:justify-between items-center gap-3 py-5">
<div className="flex order-2 md:order-1 gap-2 font-normal text-2sm">
<span className="text-gray-500">{currentYear}&copy;</span>
<a href="https://" target="_blank" className="text-gray-600 hover:text-primary">
Brillian Dev.
</a>
</div>
</div>
</Container>
</footer>
);
};
export { Footer };

View File

@ -0,0 +1 @@
export * from './Footer';

View File

@ -0,0 +1,38 @@
import clsx from 'clsx';
import { Container } from '@/components/container';
import { HeaderLogo, HeaderTopbar } from '.';
import { useDemo2Layout } from '../';
import { useEffect } from 'react';
import { toAbsoluteUrl } from '@/utils';
const Header = () => {
const { headerSticky } = useDemo2Layout();
useEffect(() => {
if (headerSticky) {
document.body.setAttribute('data-sticky-header', 'on');
} else {
document.body.removeAttribute('data-sticky-header');
}
}, [headerSticky]);
return (
<header
className={clsx(
'flex items-center transition-[height] shrink-0 h-[--tw-header-height] bg-[length:600px] bg-no-repeat',
headerSticky &&
'transition-[height] fixed z-10 top-0 left-0 right-0 shadow-sm backdrop-blur-md bg-white/70 dark:bg-coal-500/70 dark:border-b dark:border-b-coal-100'
)}
style={{
backgroundImage: `url('${toAbsoluteUrl('/media/images/2600x1200/bg-14.png')}')`
}}
>
<Container className="flex justify-between items-center lg:gap-4">
<HeaderLogo />
<HeaderTopbar />
</Container>
</header>
);
};
export { Header };

View File

@ -0,0 +1,104 @@
import { Link, useLocation } from 'react-router-dom';
import { KeenIcon } from '@/components/keenicons';
import { toAbsoluteUrl } from '@/utils';
import {
Menu,
MenuArrow,
MenuIcon,
MenuItem,
MenuLink,
MenuSub,
MenuTitle,
MenuToggle
} from '@/components/menu';
import { MENU_ROOT } from '@/config';
import { useEffect, useState } from 'react';
import { useLanguage } from '@/i18n';
const HeaderLogo = () => {
const { pathname } = useLocation();
const { isRTL } = useLanguage();
const [selectedMenuItem, setSelectedMenuItem] = useState(MENU_ROOT[0]);
useEffect(() => {
MENU_ROOT.forEach((item) => {
if (item.rootPath && pathname.includes(item.rootPath)) {
setSelectedMenuItem(item);
}
});
}, [pathname]);
return (
// <div className="flex items-center gap-2 lg:gap-5 2xl:-ml-[60px]">
<div className="flex items-center gap-2 lg:gap-5">
<Link to="/" className="shrink-0">
{/* <img
src={toAbsoluteUrl('/media/app/mini-logo-circle.svg')}
className="dark:hidden min-h-[42px]"
alt="logo"
/>
<img
src={toAbsoluteUrl('/media/app/mini-logo-circle-dark.svg')}
className="hidden dark:inline-block min-h-[42px]"
alt="logo"
/> */}
<img
src={toAbsoluteUrl('/media/app/bri_tl_logo.png')}
className="dark:hidden h-10"
alt="logo"
/>
<img
src={toAbsoluteUrl('/media/app/bri_tl_logo.png')}
className="hidden dark:inline-block min-h-[42px]"
alt="logo"
/>
</Link>
<div className="flex items-center">
<h3 className="text-gray-700 text-base hidden md:block">Brillian Apps</h3>
<span className="text-sm text-gray-400 font-medium px-2.5 hidden md:inline">/</span>
<Menu className="menu-default">
<MenuItem
toggle="dropdown"
trigger="hover"
dropdownProps={{
placement: isRTL() ? 'bottom-end' : 'bottom-start',
modifiers: [
{
name: 'offset',
options: {
offset: [0, 10] // [skid, distance]
}
}
]
}}
>
<MenuToggle className="text-gray-900 font-medium">
{selectedMenuItem.title}
<MenuArrow>
<KeenIcon icon="down" />
</MenuArrow>
</MenuToggle>
<MenuSub className="menu-default w-48">
{MENU_ROOT.map((item, index) => (
<MenuItem key={index} className={item === selectedMenuItem ? 'active' : ''}>
<MenuLink path={item.path}>
{item.icon && (
<MenuIcon>
<KeenIcon icon={item.icon} />
</MenuIcon>
)}
<MenuTitle>{item.title}</MenuTitle>
</MenuLink>
</MenuItem>
))}
</MenuSub>
</MenuItem>
</Menu>
</div>
</div>
);
};
export { HeaderLogo };

View File

@ -0,0 +1,51 @@
import { useRef } from 'react';
import { KeenIcon } from '@/components/keenicons';
import { toAbsoluteUrl } from '@/utils';
import { Menu, MenuItem, MenuToggle } from '@/components';
import { DropdownUser } from '@/partials/dropdowns/user';
import { useLanguage } from '@/i18n';
const HeaderTopbar = () => {
const itemChatRef = useRef<any>(null);
const itemUserRef = useRef<any>(null);
const itemNotificationsRef = useRef<any>(null);
const { isRTL } = useLanguage();
const handleDropdownChatShow = () => {
window.dispatchEvent(new Event('resize'));
};
return (
<div className="flex items-center gap-3.5">
<Menu>
<MenuItem
ref={itemUserRef}
toggle="dropdown"
trigger="click"
dropdownProps={{
placement: isRTL() ? 'bottom-start' : 'bottom-end',
modifiers: [
{
name: 'offset',
options: {
offset: [20, 10] // [skid, distance]
}
}
]
}}
>
<MenuToggle className="btn btn-icon rounded-full">
<img
className="size-9 rounded-full justify-center border border-gray-500 shrink-0"
src={toAbsoluteUrl('/media/avatars/blank.png')}
alt=""
/>
</MenuToggle>
{DropdownUser({ menuItemRef: itemUserRef })}
</MenuItem>
</Menu>
</div>
);
};
export { HeaderTopbar };

View File

@ -0,0 +1,3 @@
export * from './Header';
export * from './HeaderLogo';
export * from './HeaderTopbar';

View File

@ -0,0 +1,8 @@
export * from './Demo2Layout';
export * from './Demo2LayoutConfig';
export * from './Demo2LayoutProvider';
export * from './main';
export * from './header';
export * from './navbar';
export * from './toolbar';
export * from './footer';

View File

@ -0,0 +1,38 @@
import { useMenuCurrentItem } from '@/components/menu';
import { useMenus } from '@/providers';
import { Fragment, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import { Outlet, useLocation } from 'react-router';
import { Footer, Header, Navbar } from '../';
import { Toolbar, ToolbarHeading } from '../toolbar';
const Main = () => {
const { pathname } = useLocation();
const { getMenuConfig } = useMenus();
const menuConfig = getMenuConfig('primary');
const menuItem = useMenuCurrentItem(pathname, menuConfig);
return (
<Fragment>
<Helmet>
<title>{menuItem?.title}</title>
</Helmet>
<div className="flex grow flex-col [[data-sticky-header=on]_&]:pt-[--tw-header-height-default]">
<Header />
<Navbar />
<main className="grow" role="content">
<Toolbar>
<ToolbarHeading />
</Toolbar>
<Outlet />
</main>
<Footer />
</div>
</Fragment>
);
};
export { Main };

View File

@ -0,0 +1 @@
export * from './Main';

View File

@ -0,0 +1,14 @@
import { Container } from '@/components/container';
import { NavbarMenu } from '../';
const Navbar = () => {
return (
<div className="border-b border-gray-200 pb-5 lg:pb-0 mb-5 lg:mb-5">
<Container className="flex flex-wrap justify-between items-center gap-2">
<NavbarMenu />
</Container>
</div>
);
};
export { Navbar };

View File

@ -0,0 +1,159 @@
import { KeenIcon } from '@/components/keenicons';
import {
Menu,
MenuArrow,
TMenuConfig,
MenuItem,
MenuLink,
MenuSub,
MenuTitle
} from '@/components/menu';
import { useMenus } from '@/providers';
// import { useLocation } from 'react-router';
import { useLanguage } from '@/i18n';
import { doGetNavbarMenu } from '@/actions/NavbarMenuActions';
import { RoleList, MappedMenu } from '@/types/GlobalTypes';
import { useEffect, useState } from 'react';
const NavbarMenu = () => {
// const { pathname } = useLocation();
// const { getMenuConfig } = useMenus();
// const primaryMenu = getMenuConfig('primary');
const { isRTL } = useLanguage();
const [menuData, setMenuData] = useState<RoleList[]>([]);
const [error, setError] = useState<string | null>(null);
let navbarMenu;
useEffect(() => {
const fetchNavbarMenu = async () => {
try {
const result = await doGetNavbarMenu();
if (result.status && result.data) {
setMenuData(result.data.roles_list);
} else {
setError(result.message);
}
} catch (err) {
setError('Failed to fetch navbar menu');
}
};
fetchNavbarMenu();
}, []);
const mapMenuData = (data: RoleList[]): MappedMenu[] => {
return data.map((item) => {
const mappedItem: MappedMenu = {
title: item.name,
path: item.link !== '-' ? item.link : '/'
};
if (item.children && item.children.length > 0) {
mappedItem.children = mapMenuData(item.children);
}
return mappedItem;
});
};
navbarMenu = mapMenuData(menuData);
// navbarMenu = primaryMenu?.[0].children;
const buildMenu = (items: TMenuConfig) => {
return items.map((item, index) => {
if (item.children) {
return (
<MenuItem
key={index}
className="border-b-2 border-b-transparent menu-item-active:border-b-gray-900 menu-item-here:border-b-gray-900"
trigger="hover"
toggle="dropdown"
dropdownProps={{
placement: isRTL() ? 'bottom-end' : 'bottom-start'
}}
>
<MenuLink className="gap-1.5 pb-2 lg:pb-4">
<MenuTitle className="text-nowrap text-sm text-gray-800 menu-item-active:text-gray-900 menu-item-active:font-medium menu-item-here:text-gray-900 menu-item-here:font-medium menu-item-show:text-gray-900 menu-link-hover:text-gray-900">
{item.title}
</MenuTitle>
<MenuArrow>
<KeenIcon icon="down" className="text-2xs text-gray-500" />
</MenuArrow>
</MenuLink>
<MenuSub className="menu-default py-2" rootClassName="min-w-[200px]">
{buildMenuChildren(item.children)}
</MenuSub>
</MenuItem>
);
} else if (!item.disabled) {
return (
<MenuItem
key={index}
className="border-b-2 border-b-transparent menu-item-active:border-b-gray-900 menu-item-here:border-b-gray-900"
>
<MenuLink path={item.path} className="gap-2.5 pb-2 lg:pb-4">
<MenuTitle className="text-nowrap text-sm text-gray-800 menu-item-active:text-gray-900 menu-item-active:font-medium menu-item-here:text-gray-900 menu-item-here:font-medium menu-item-show:text-gray-900 menu-link-hover:text-gray-900">
{item.title}
</MenuTitle>
</MenuLink>
</MenuItem>
);
}
});
};
const buildMenuChildren = (items: TMenuConfig) => {
return items.map((item, index) => {
if (item.children) {
return (
<MenuItem
key={index}
trigger="hover"
toggle="dropdown"
dropdownProps={{
placement: isRTL() ? 'left-start' : 'right-start',
modifiers: [
{
name: 'offset',
options: {
offset: [-10, 0]
}
}
]
}}
>
<MenuLink>
<MenuTitle>{item.title}</MenuTitle>
<MenuArrow>
<KeenIcon icon="down" className="text-2xs [.menu-dropdown_&]:-rotate-90" />
</MenuArrow>
</MenuLink>
<MenuSub className="menu-default" rootClassName="min-w-[200px]">
{buildMenuChildren(item.children)}
</MenuSub>
</MenuItem>
);
} else if (!item.disabled) {
return (
<MenuItem key={index}>
<MenuLink path={item.path}>
<MenuTitle>{item.title}</MenuTitle>
</MenuLink>
</MenuItem>
);
}
});
};
return (
<div className="grid">
<div className="scrollable-x-auto">
<Menu highlight={true} className="gap-5 lg:gap-7.5">
{navbarMenu && navbarMenu && buildMenu(navbarMenu)}
</Menu>
</div>
</div>
);
};
export { NavbarMenu };

View File

@ -0,0 +1,2 @@
export * from './Navbar';
export * from './NavbarMenu';

View File

@ -0,0 +1,18 @@
import { Container } from '@/components';
import { ReactNode } from 'react';
export interface IToolbarProps {
children?: ReactNode;
}
const Toolbar = ({ children }: IToolbarProps) => {
return (
<div className="mb-5 lg:mb-5">
<Container className="flex items-center justify-between flex-wrap gap-5">
{children}
</Container>
</div>
);
};
export { Toolbar };

View File

@ -0,0 +1,11 @@
import { ReactNode } from 'react';
export interface IToolbarActionsProps {
children?: ReactNode;
}
const ToolbarActions = ({ children }: IToolbarActionsProps) => {
return <div className="flex items-center gap-1">{children}</div>;
};
export { ToolbarActions };

View File

@ -0,0 +1,32 @@
import { Fragment } from 'react';
import { useMenuBreadcrumbs } from '@/components';
import { Link } from 'react-router-dom';
import { useMenus } from '@/providers';
import { useLocation } from 'react-router';
const ToolbarBreadcrumbs = () => {
const { getMenuConfig } = useMenus();
const { pathname } = useLocation();
const items = useMenuBreadcrumbs(pathname, getMenuConfig('primary'));
return (
<div className="flex items-center gap-1 text-sm font-normal">
{items.map((item, index) => (
<Fragment key={index}>
{item.path ? (
<Link to={item.path} className="text-gray-700 hover:text-primary">
{item.title}
</Link>
) : (
<span className={index === items.length - 1 ? 'text-gray-900' : 'text-gray-700'}>
{item.title}
</span>
)}
{index !== items.length - 1 && <span className="text-gray-400 text-sm">/</span>}
</Fragment>
))}
</div>
);
};
export { ToolbarBreadcrumbs };

View File

@ -0,0 +1,24 @@
import { ReactNode } from 'react';
import { useMenus } from '@/providers';
import { useMenuCurrentItem } from '@/components';
import { useLocation } from 'react-router';
import { ToolbarBreadcrumbs } from './ToolbarBreadcrumbs';
export interface IToolbarHeadingProps {
title?: string | ReactNode;
}
const ToolbarHeading = ({ title = '' }: IToolbarHeadingProps) => {
const { getMenuConfig } = useMenus();
const { pathname } = useLocation();
const currentMenuItem = useMenuCurrentItem(pathname, getMenuConfig('primary'));
return (
<div className="flex items-center flex-wrap gap-1 lg:gap-5">
<h1 className="font-medium text-lg text-gray-900">{title || currentMenuItem?.title}</h1>
<ToolbarBreadcrumbs />
</div>
);
};
export { ToolbarHeading };

View File

@ -0,0 +1,4 @@
export * from './Toolbar';
export * from './ToolbarBreadcrumbs';
export * from './ToolbarHeading';
export * from './ToolbarActions';

View File

@ -0,0 +1,18 @@
import { Outlet } from 'react-router-dom';
import { ErrorsLayoutProvider } from './ErrorsLayoutProvider';
const Layout = () => {
return (
<div className="flex flex-col items-center justify-center grow h-[95%]">
<Outlet />
</div>
);
};
const ErrorsLayout = () => (
<ErrorsLayoutProvider>
<Layout />
</ErrorsLayoutProvider>
);
export { ErrorsLayout };

View File

@ -0,0 +1,9 @@
import { type ILayoutConfig } from '@/providers';
// Defining the layout configuration for the errors layout
const errorsLayoutConfig: ILayoutConfig = {
name: 'errors-layout', // Unique identifier for the layout
options: {} // Placeholder for layout options, can be customized later
};
export { errorsLayoutConfig };

View File

@ -0,0 +1,51 @@
import { createContext, type PropsWithChildren, useContext, useEffect, useState } from 'react';
import { deepMerge } from '@/utils';
import { ILayoutConfig, useLayout } from '@/providers';
import { errorsLayoutConfig } from './ErrorsLayoutConfig';
// Interface defining the properties for the AuthLayoutProvider
interface AuthLayoutProviderProps {
layout: ILayoutConfig; // The layout configuration object
}
// Initial layout properties using the errors layout configuration
const initalLayoutProps: AuthLayoutProviderProps = {
layout: errorsLayoutConfig // Initial layout is set to errorsLayoutConfig
};
// Creating a context for managing layout-related state and logic
const LayoutContext = createContext<AuthLayoutProviderProps>(initalLayoutProps);
// Custom hook to access the layout context, simplifying its use in other components
const useErrorsLayout = () => useContext(LayoutContext);
// Provider component that manages the state and context for the Errors layout
const ErrorsLayoutProvider = ({ children }: PropsWithChildren) => {
const { getLayout, setCurrentLayout } = useLayout(); // Hook to get and set the layout configuration
// Function to get and merge the layout configuration
const getLayoutConfig = () => {
return deepMerge(errorsLayoutConfig, getLayout(errorsLayoutConfig.name)); // Merge errors layout config with any layout changes from getLayout
};
// State that holds the current layout configuration
const [layout] = useState(getLayoutConfig); // Initializing layout state with merged configuration
// Effect to set the current layout whenever the layout state changes
useEffect(() => {
setCurrentLayout(layout); // Sets the layout context to the current layout configuration
});
return (
<LayoutContext.Provider
value={{
layout // Providing the layout object to child components
}}
>
{children}
</LayoutContext.Provider>
);
};
// eslint-disable-next-line react-refresh/only-export-components
export { ErrorsLayoutProvider, useErrorsLayout };

View File

@ -0,0 +1,2 @@
export * from './ErrorsLayout';
export * from './ErrorsLayoutConfig';