WIP Chakra site wrapper

This commit is contained in:
Jamie Curnow 2021-11-15 06:47:51 +10:00
parent 8a6a355097
commit eff9c45ea8
22 changed files with 262 additions and 876 deletions

View file

@ -1,2 +1,4 @@
PORT=9000
IMAGE_INLINE_SIZE_LIMIT=20000
REACT_APP_VERSION=development
REACT_APP_COMMIT=abcdef1

View file

@ -1,130 +0,0 @@
import React, { ReactNode, useState } from "react";
import cn from "classnames";
import { AlertLink } from "./AlertLink";
export interface AlertProps {
/**
* Child elements within
*/
children?: ReactNode;
/**
* Additional Class
*/
className?: string;
/**
* The type of this Alert, changes it's color
*/
type?: "info" | "success" | "warning" | "danger";
/**
* Alert Title
*/
title?: string;
/**
* An Icon to be displayed on the right hand side of the Alert
*/
icon?: ReactNode;
/**
* Display an Avatar on the left hand side of this Alert
*/
avatar?: ReactNode;
/**
*
*/
important?: boolean;
/**
* Adds an 'X' to the right side of the Alert that dismisses the Alert
*/
dismissable?: boolean;
/**
* Event to call after dissmissing
*/
onDismissClick?: React.MouseEventHandler<HTMLButtonElement>;
}
export const Alert: React.FC<AlertProps> = ({
children,
className,
type = "info",
title,
icon,
avatar,
important = false,
dismissable = false,
onDismissClick,
}) => {
const [dismissed, setDismissed] = useState(false);
const classes = {
"alert-dismissible": dismissable,
"alert-important": important,
};
const handleDismissed = (
e: React.MouseEvent<HTMLButtonElement, MouseEvent>,
) => {
setDismissed(true);
onDismissClick && onDismissClick(e);
};
const wrappedTitle = title ? <h4 className="alert-title">{title}</h4> : null;
const wrappedChildren =
children && !important ? (
<div className="text-muted">{children}</div>
) : (
children
);
const wrapIfIcon = (): ReactNode => {
if (avatar) {
return (
<div className="d-flex">
<div>
<span className="float-start me-3">{avatar}</span>
</div>
<div>{wrappedChildren}</div>
</div>
);
}
if (icon) {
return (
<div className="d-flex">
<div>
<span className="alert-icon">{icon}</span>
</div>
<div>
{wrappedTitle}
{wrappedChildren}
</div>
</div>
);
}
return (
<>
{wrappedTitle}
{wrappedChildren}
</>
);
};
if (!dismissed) {
return (
<div
className={cn("alert", `alert-${type}`, classes, className)}
role="alert">
{wrapIfIcon()}
{dismissable ? (
<button
className="btn-close"
data-bs-dismiss="alert"
aria-label="close"
onClick={handleDismissed}
/>
) : null}
</div>
);
}
return null;
};
Alert.Link = AlertLink;

View file

@ -1,34 +0,0 @@
import React, { ReactNode } from "react";
import cn from "classnames";
export interface AlertLinkProps {
/**
* Child elements within
*/
children?: ReactNode;
/**
* Additional Class
*/
className?: string;
/**
* Href
*/
href?: string;
/**
* onClick handler
*/
onClick?: React.MouseEventHandler<HTMLAnchorElement>;
}
export const AlertLink: React.FC<AlertLinkProps> = ({
children,
className,
href,
onClick,
}) => {
return (
<a className={cn("alert-link", className)} href={href} onClick={onClick}>
{children}
</a>
);
};

View file

@ -1 +0,0 @@
export * from "./Alert";

View file

@ -1,80 +1,77 @@
import React from "react";
import { useHealthState } from "context";
import {
Box,
Container,
Stack,
Text,
useColorModeValue,
} from "@chakra-ui/react";
import { intl } from "locale";
function Footer() {
const { health } = useHealthState();
return (
<footer className="footer footer-transparent d-print-none">
<div className="container">
<div className="row text-center align-items-center flex-row-reverse">
<div className="col-lg-auto ms-lg-auto">
<ul className="list-inline list-inline-dots mb-0">
<li className="list-inline-item">
<a
href="https://nginxproxymanager.com?utm_source=npm"
target="_blank"
rel="noreferrer"
className="link-secondary">
{intl.formatMessage({
id: "footer.userguide",
defaultMessage: "User Guide",
})}
</a>
</li>
<li className="list-inline-item">
<a
href="https://github.com/jc21/nginx-proxy-manager/releases?utm_source=npm"
target="_blank"
rel="noreferrer"
className="link-secondary">
{intl.formatMessage({
id: "footer.changelog",
defaultMessage: "Change Log",
})}
</a>
</li>
<li className="list-inline-item">
<a
href="https://github.com/jc21/nginx-proxy-manager?utm_source=npm"
target="_blank"
rel="noreferrer"
className="link-secondary">
{intl.formatMessage({
id: "footer.github",
defaultMessage: "Github",
})}
</a>
</li>
</ul>
</div>
<div className="col-12 col-lg-auto mt-3 mt-lg-0">
<ul className="list-inline list-inline-dots mb-0">
<li className="list-inline-item">
{intl.formatMessage(
{
id: "footer.copyright",
defaultMessage: "Copyright © {year} jc21.com",
},
{ year: new Date().getFullYear() },
)}
</li>
<li className="list-inline-item">
<a
href="https://github.com/jc21/nginx-proxy-manager/releases?utm_source=npm"
target="_blank"
className="link-secondary"
rel="noopener noreferrer">
v{health.version} {String.fromCharCode(183)} {health.commit}
</a>
</li>
</ul>
</div>
</div>
</div>
</footer>
<Box
bg={useColorModeValue("gray.50", "gray.900")}
color={useColorModeValue("gray.700", "gray.200")}>
<Container
as={Stack}
maxW={"6xl"}
py={4}
direction={{ base: "column", md: "row" }}
spacing={4}
justify={{ base: "center", md: "space-between" }}
align={{ base: "center", md: "center" }}>
<Text>
{intl.formatMessage(
{
id: "footer.copyright",
defaultMessage: "Copyright © {year} jc21.com",
},
{ year: new Date().getFullYear() },
)}
</Text>
<Stack direction={"row"} spacing={6}>
<a
href="https://nginxproxymanager.com?utm_source=npm"
target="_blank"
rel="noreferrer"
className="link-secondary">
{intl.formatMessage({
id: "footer.userguide",
defaultMessage: "User Guide",
})}
</a>
<a
href="https://github.com/jc21/nginx-proxy-manager/releases?utm_source=npm"
target="_blank"
rel="noreferrer"
className="link-secondary">
{intl.formatMessage({
id: "footer.changelog",
defaultMessage: "Change Log",
})}
</a>
<a
href="https://github.com/jc21/nginx-proxy-manager?utm_source=npm"
target="_blank"
rel="noreferrer"
className="link-secondary">
{intl.formatMessage({
id: "footer.github",
defaultMessage: "Github",
})}
</a>
<a
href="https://github.com/jc21/nginx-proxy-manager/releases?utm_source=npm"
target="_blank"
rel="noopener noreferrer">
v{process.env.REACT_APP_VERSION} {String.fromCharCode(183)}{" "}
{process.env.REACT_APP_COMMIT}
</a>
</Stack>
</Container>
</Box>
);
}

View file

@ -0,0 +1,99 @@
import React from "react";
import {
Box,
Flex,
Avatar,
HStack,
IconButton,
Button,
Menu,
MenuButton,
MenuList,
MenuItem,
MenuDivider,
useDisclosure,
useColorModeValue,
Stack,
} from "@chakra-ui/react";
import { LocalePicker, ThemeSwitcher } from "components";
import { FaBars, FaTimes } from "react-icons/fa";
import logo from "../../img/logo-256.png";
import { NavLink } from "./NavLink";
const Links = ["Dashboard", "Projects", "Team"];
export const Nav = () => {
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<>
<Box bg={useColorModeValue("gray.100", "gray.900")} px={4}>
<Flex h={16} alignItems={"center"} justifyContent={"space-between"}>
<IconButton
size={"md"}
icon={isOpen ? <FaTimes /> : <FaBars />}
aria-label={"Open Menu"}
display={{ md: "none" }}
onClick={isOpen ? onClose : onOpen}
/>
<HStack spacing={8} alignItems={"center"}>
<Box>
<img src={logo} width={32} alt="Logo" />
</Box>
<HStack
as={"nav"}
spacing={4}
display={{ base: "none", md: "flex" }}>
{Links.map((link) => (
<NavLink key={link}>{link}</NavLink>
))}
</HStack>
</HStack>
<Flex alignItems={"center"}>
<Stack h={10} m={4} justify={"end"} direction={"row"}>
<ThemeSwitcher />
<LocalePicker className="text-right" />
</Stack>
<Menu>
<MenuButton
as={Button}
rounded={"full"}
variant={"link"}
cursor={"pointer"}
minW={0}>
<Avatar
size={"sm"}
src={
"https://images.unsplash.com/photo-1493666438817-866a91353ca9?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&fit=crop&h=200&w=200&s=b616b2c5b373a80ffc9636ba24f7a4a9"
}
/>
</MenuButton>
<MenuList>
<MenuItem>Link 1</MenuItem>
<MenuItem>Link 2</MenuItem>
<MenuDivider />
<MenuItem>Link 3</MenuItem>
</MenuList>
</Menu>
</Flex>
</Flex>
{isOpen ? (
<Box pb={4} display={{ md: "none" }}>
<Stack as={"nav"} spacing={4}>
{Links.map((link) => (
<NavLink key={link}>{link}</NavLink>
))}
</Stack>
</Box>
) : null}
</Box>
<Box p={4}>Main Content Here</Box>
</>
);
};
Nav.Link = NavLink;

View file

@ -0,0 +1,25 @@
import React, { ReactNode } from "react";
import { Link, useColorModeValue } from "@chakra-ui/react";
export interface NavLinkProps {
/**
* Child elements within
*/
children?: ReactNode;
}
export const NavLink: React.FC<NavLinkProps> = ({ children }) => {
return (
<Link
px={2}
py={1}
rounded={"md"}
_hover={{
textDecoration: "none",
bg: useColorModeValue("gray.200", "gray.700"),
}}
href={"#"}>
{children}
</Link>
);
};

View file

@ -0,0 +1 @@
export * from "./Nav";

View file

@ -1,104 +0,0 @@
import React from "react";
import { Dropdown, Navigation } from "components";
import { intl } from "locale";
import {
Book,
DeviceDesktop,
Home,
Lock,
Settings,
Shield,
Users,
} from "tabler-icons-react";
const NavMenu: React.FC<{ openOnMobile: boolean }> = ({ openOnMobile }) => {
return (
<Navigation.Menu
theme="light"
className="mb-3"
withinHeader={true}
openOnMobile={openOnMobile}
items={[
{
title: intl.formatMessage({
id: "dashboard.title",
defaultMessage: "Dashboard",
}),
icon: <Home />,
to: "/",
},
{
title: intl.formatMessage({
id: "hosts.title",
defaultMessage: "Hosts",
}),
icon: <DeviceDesktop />,
to: "/hosts",
},
{
title: intl.formatMessage({
id: "accesslists.title",
defaultMessage: "Access Lists",
}),
icon: <Lock />,
to: "/access-lists",
},
{
title: "SSL",
icon: <Shield />,
dropdownItems: [
<Dropdown.Item
key="ssl-certificates"
to="/ssl/certificates"
role="button">
<span className="nav-link-title">
{intl.formatMessage({
id: "certificates.title",
defaultMessage: "Certificates",
})}
</span>
</Dropdown.Item>,
<Dropdown.Item
key="ssl-authorities"
to="/ssl/authorities"
role="button">
<span className="nav-link-title">
{intl.formatMessage({
id: "cert_authorities.title",
defaultMessage: "Certificate Authorities",
})}
</span>
</Dropdown.Item>,
],
},
{
title: intl.formatMessage({
id: "auditlog.title",
defaultMessage: "Audit Log",
}),
icon: <Book />,
to: "/audit-log",
},
{
title: intl.formatMessage({
id: "users.title",
defaultMessage: "Users",
}),
icon: <Users />,
to: "/users",
},
{
title: intl.formatMessage({
id: "settings.title",
defaultMessage: "Settings",
}),
icon: <Settings />,
to: "/settings",
},
]}
/>
);
};
export { NavMenu };

View file

@ -1,19 +0,0 @@
import { ReactNode } from "react";
import { NavigationHeader } from "./NavigationHeader";
import { NavigationMenu } from "./NavigationMenu";
import { NavigationMenuItem } from "./NavigationMenuItem";
export interface NavigationProps {
/**
* Child elements within
*/
children?: ReactNode;
}
export const Navigation = ({ children }: NavigationProps) => {
return children;
};
Navigation.Header = NavigationHeader;
Navigation.Menu = NavigationMenu;
Navigation.MenuItem = NavigationMenuItem;

View file

@ -1,211 +0,0 @@
import React, { ReactNode, useState, useRef, useEffect } from "react";
import cn from "classnames";
import { Bell } from "tabler-icons-react";
import { NavMenu } from "..";
import { Badge } from "../Badge";
import { ButtonList } from "../ButtonList";
import { Dropdown } from "../Dropdown";
import { NavigationMenu } from "./NavigationMenu";
import { NavigationMenuItemProps } from "./NavigationMenuItem";
export interface NavigationHeaderProps {
/**
* Additional Class
*/
className?: string;
/**
* Logo and/or Text elements to show on the left brand side of the header
*/
brandContent?: ReactNode;
/**
* Color theme for the nav bar
*/
theme?: "transparent" | "light" | "dark";
/**
* Buttons to show in the header
*/
buttons?: ReactNode[];
/**
* Notifications Content
*/
notifications?: ReactNode;
/**
* Has unread notifications, shows red dot
*/
hasUnreadNotifications?: boolean;
/**
* Avatar Object
*/
avatar?: ReactNode;
/**
* Profile name to show next to avatar
*/
profileName?: string;
/**
* Profile text to show beneath profileName
*/
profileSubName?: string;
/**
* Profile dropdown menu items
*/
profileItems?: ReactNode[];
/**
* Applies dark theme to Notifications and Profile dropdowns
*/
darkDropdowns?: boolean;
/**
* Navigation Menu within this Header
*/
menuItems?: NavigationMenuItemProps[];
}
export const NavigationHeader: React.FC<NavigationHeaderProps> = ({
className,
theme = "transparent",
brandContent,
buttons,
notifications,
hasUnreadNotifications,
avatar,
profileName,
profileSubName,
profileItems,
darkDropdowns,
menuItems,
}) => {
const [notificationsShown, setNotificationsShown] = useState(false);
const [profileShown, setProfileShown] = useState(false);
const [mobileNavShown, setMobileNavShown] = useState(false);
const profileRef = useRef(null);
const notificationsRef = useRef(null);
const toggleMobileNavShown = () =>
setMobileNavShown((prevState) => {
return !prevState;
});
const handleClickOutside = (event: any) => {
if (
profileRef.current &&
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
!profileRef.current.contains(event.target)
) {
setProfileShown(false);
}
if (
notificationsRef.current &&
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
!notificationsRef.current.contains(event.target)
) {
setNotificationsShown(false);
}
};
useEffect(() => {
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
return (
<>
<header
className={cn(
`navbar navbar-expand-md navbar-${theme} d-print-none`,
className,
)}>
<div className="container-xl">
<button
className="navbar-toggler"
type="button"
onClick={toggleMobileNavShown}>
<span className="navbar-toggler-icon" />
</button>
<h1 className="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-md-3">
{brandContent}
</h1>
<div className="navbar-nav flex-row order-md-last">
{buttons ? (
<div className="nav-item d-none d-md-flex me-3">
<ButtonList>{buttons}</ButtonList>
</div>
) : null}
{notifications ? (
<div
className="nav-item dropdown d-none d-md-flex me-3"
ref={notificationsRef}>
<button
style={{
border: 0,
backgroundColor: "transparent",
}}
className="nav-link px-0"
aria-label="Show notifications"
onClick={() => {
setNotificationsShown(!notificationsShown);
}}>
<Bell className="icon" />
{hasUnreadNotifications ? <Badge color="red" /> : null}
</button>
<Dropdown
className="dropdown-menu-end dropdown-menu-card"
show={notificationsShown}
dark={darkDropdowns}>
<div className="card">
<div className="card-body">{notifications}</div>
</div>
</Dropdown>
</div>
) : null}
<div
ref={profileRef}
className={cn("nav-item", {
dropdown: !!profileItems,
})}>
<button
style={{
border: 0,
backgroundColor: "transparent",
}}
className="nav-link d-flex lh-1 text-reset p-0"
aria-label={profileItems && "Open user menu"}
onClick={() => {
setProfileShown(!profileShown);
}}>
{avatar}
{profileName ? (
<div className="d-none d-xl-block ps-2">
<div style={{ textAlign: "left" }}>{profileName}</div>
{profileSubName ? (
<div
className="mt-1 small text-muted"
style={{ textAlign: "left" }}>
{profileSubName}
</div>
) : null}
</div>
) : null}
</button>
{profileItems ? (
<Dropdown
className="dropdown-menu-end dropdown-menu-card"
show={profileShown}
dark={darkDropdowns}
arrow>
{profileItems}
</Dropdown>
) : null}
</div>
</div>
{menuItems ? (
<NavigationMenu
items={menuItems}
withinHeader
openOnMobile={false}
/>
) : null}
</div>
</header>
<NavMenu openOnMobile={mobileNavShown} />
</>
);
};

View file

@ -1,130 +0,0 @@
import React, { ReactNode, useState, useRef, useEffect } from "react";
import cn from "classnames";
import styled from "styled-components";
import {
NavigationMenuItem,
NavigationMenuItemProps,
} from "./NavigationMenuItem";
/**
* This menu handles the state of the dropdowns being shown, instead of state
* being handled within the NavigationItem object, because we want the behaviour
* of clicking one menu item with a dropdown to close the already open dropdown
* of another menu item. This can only be done if we handle state one level above
* the items.
*/
const StyledNavWrapper = styled.div<{ shown: boolean }>`
@media (max-width: 767.98px) {
transition: max-height 300ms ease-in-out;
max-height: ${(p) => (p.shown ? `80vh` : `0`)};
min-height: ${(p) => (p.shown ? `inherit` : `0`)};
overflow: hidden;
padding: ${(p) => (p.shown ? `inherit` : `0`)};
}
`;
export interface NavigationMenuProps {
/** Additional Class */
className?: string;
/** Navigation Items */
items: NavigationMenuItemProps[];
/** If this menu sits within a Navigation.Header */
withinHeader?: boolean;
/** Color theme for the nav bar */
theme?: "transparent" | "light" | "dark";
/** Search content */
searchContent?: ReactNode;
/** Navigation is currently hidden on mobile */
openOnMobile?: boolean;
}
export const NavigationMenu: React.FC<NavigationMenuProps> = ({
className,
items,
withinHeader,
theme = "transparent",
searchContent,
openOnMobile = true,
}) => {
const [dropdownShown, setDropdownShown] = useState(0);
const navRef = useRef(null);
const handleClickOutside = (event: any) => {
if (
navRef.current &&
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
!navRef.current.contains(event.target)
) {
setDropdownShown(0);
}
};
useEffect(() => {
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
const itemClicked = (
e: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
item: NavigationMenuItemProps,
idx: number,
) => {
setDropdownShown(dropdownShown === idx ? 0 : idx);
item.onClick && item.onClick(e);
};
const wrapMenu = (el: ReactNode) => {
if (withinHeader) {
return (
<div className="navbar-expand-md">
<StyledNavWrapper
shown={openOnMobile}
className={cn(`navbar navbar-${theme} navbar-collapse`, className)}>
<div className="container-xl">{el}</div>
</StyledNavWrapper>
</div>
);
}
return (
<div className={"navbar-expand-md"}>
<div
className={cn(`navbar navbar-${theme}`, className)}
id="navbar-menu">
<div className="container-xl">
{el}
{searchContent ? (
<div className="my-2 my-md-0 flex-grow-1 flex-md-grow-0 order-first order-md-last">
{searchContent}
</div>
) : null}
</div>
</div>
</div>
);
};
return wrapMenu(
<ul className="navbar-nav" ref={navRef}>
{items.map((item: any, idx: number) => {
const onClickItem = (
e: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
) => {
itemClicked(e, item, idx);
};
return (
<NavigationMenuItem
key={`navmenu-${idx}`}
onClick={onClickItem}
dropdownShow={dropdownShown === idx}
activeOnlyWhenExact
{...item}
/>
);
})}
</ul>,
);
};
NavigationMenu.Item = NavigationMenuItem;

View file

@ -1,122 +0,0 @@
import React, { ReactNode } from "react";
import cn from "classnames";
import { Link, useRouteMatch } from "react-router-dom";
import { Dropdown } from "../Dropdown";
export interface NavigationMenuItemProps {
/**
* Additional Class
*/
className?: string;
/**
* An Icon to be displayed on the right hand side of the Alert
*/
icon?: ReactNode;
/**
* Title of the Item
*/
title: string;
/**
* Href if this is navigating somewhere
*/
href?: string;
/**
* target property, only used when href is set
*/
target?: string;
/**
* Router Link to if using react-router-dom
*/
to?: string;
/**
* Router Link property if using react-router-dom
*/
activeOnlyWhenExact?: boolean;
/**
* On click handler
*/
onClick?: React.MouseEventHandler<HTMLAnchorElement>;
/**
* Provide dropdown items if this is to be a dropdown menu
*/
dropdownItems?: ReactNode[];
/**
* State of the dropdown being shown
*/
dropdownShow?: boolean;
/**
* Applies dark theme to dropdown
*/
darkDropdown?: boolean;
/**
* Shows this item as being active
*/
active?: boolean;
/**
* Disables the menu item
*/
disabled?: boolean;
/**
* Badge if you want to show one
*/
badge?: ReactNode;
}
export const NavigationMenuItem: React.FC<NavigationMenuItemProps> = ({
className,
icon,
title,
href,
target,
to,
activeOnlyWhenExact,
onClick,
dropdownItems,
dropdownShow,
darkDropdown,
active,
disabled,
badge,
}) => {
const match = useRouteMatch({
path: to,
exact: activeOnlyWhenExact,
});
return (
<li
className={cn(
"nav-item",
dropdownItems && "dropdown",
{ active: match || active },
className,
)}>
<Link
to={to ?? ""}
className={cn(
"nav-link",
dropdownItems && "dropdown-toggle",
disabled && "disabled",
)}
href={href}
target={target}
role="button"
aria-expanded="false"
onClick={onClick}>
{icon && (
<span className="nav-link-icon d-md-none d-lg-inline-block">
{icon}
</span>
)}
<span className="nav-link-title">{title}</span>
{badge}
</Link>
{dropdownItems ? (
<Dropdown show={dropdownShow} dark={darkDropdown} arrow>
{dropdownItems}
</Dropdown>
) : null}
</li>
);
};

View file

@ -1 +0,0 @@
export * from "./Navigation";

View file

@ -1,10 +1,10 @@
import React, { ReactNode } from "react";
import { Footer } from "components";
import { Avatar, Dropdown, Navigation } from "components";
import { LocalePicker } from "components";
import { useAuthState, useUserState } from "context";
import { intl } from "locale";
import { Nav } from "components";
// import { LocalePicker } from "components";
// import { useAuthState, useUserState } from "context";
// import { intl } from "locale";
import styled from "styled-components";
const StyledSiteContainer = styled.div`
@ -29,13 +29,14 @@ interface Props {
children?: ReactNode;
}
function SiteWrapper({ children }: Props) {
const user = useUserState();
const { logout } = useAuthState();
// const user = useUserState();
// const { logout } = useAuthState();
return (
<StyledSiteContainer className="wrapper">
<StyledScrollContainer>
<Navigation.Header
<Nav />
{/*
theme="light"
brandContent={
<img
@ -75,6 +76,7 @@ function SiteWrapper({ children }: Props) {
</Dropdown.Item>,
]}
/>
*/}
<div className="content">
<div className="container-xl">
<StyledContentContainer>{children}</StyledContentContainer>

View file

@ -3,11 +3,13 @@ import React from "react";
import { Button, Icon, useColorMode } from "@chakra-ui/react";
import { FiSun, FiMoon } from "react-icons/fi";
export const ThemeSwitcher: React.FC = () => {
function ThemeSwitcher() {
const { colorMode, toggleColorMode } = useColorMode();
return (
<Button onClick={toggleColorMode}>
{colorMode === "light" ? <Icon as={FiMoon} /> : <Icon as={FiSun} />}
</Button>
);
};
}
export { ThemeSwitcher };

View file

@ -1,44 +1,45 @@
import React from "react";
import { Alert } from "components";
import styled from "styled-components";
const Root = styled.div`
padding: 20vh 10vw 0 10vw;
&& .ant-alert-warning {
background-color: #2a2a2a;
border: 2px solid #2ab1a4;
color: #eee;
}
&& .ant-alert-message {
color: #fff;
font-size: 6vh;
}
&& .ant-alert-description {
font-size: 4vh;
line-height: 5vh;
}
&& .ant-alert-with-description {
padding-left: 23vh;
}
&& .ant-alert-with-description .ant-alert-icon {
font-size: 15vh;
}
`;
import { Box, Flex, Heading, Text, Stack } from "@chakra-ui/react";
import { LocalePicker } from "components";
import { intl } from "locale";
import { FaTimes } from "react-icons/fa";
function Unhealthy() {
return (
<Root>
<Alert type="warning" icon="alert-triangle">
Nginx Proxy Manager is <strong>unhealthy</strong>. We'll continue to
check the health and hope to be back up and running soon!
</Alert>
</Root>
<>
<Stack h={10} m={4} justify={"end"} direction={"row"}>
<LocalePicker className="text-right" />
</Stack>
<Box textAlign="center" py={10} px={6}>
<Box display="inline-block">
<Flex
flexDirection="column"
justifyContent="center"
alignItems="center"
bg={"red.500"}
rounded={"50px"}
w={"55px"}
h={"55px"}
textAlign="center">
<FaTimes size={"30px"} color={"white"} />
</Flex>
</Box>
<Heading as="h2" size="xl" mt={6} mb={2}>
{intl.formatMessage({
id: "unhealthy.title",
defaultMessage: "Nginx Proxy Manager is unhealthy",
})}
</Heading>
<Text color={"gray.500"}>
{intl.formatMessage({
id: "unhealthy.body",
defaultMessage:
"We'll continue to check the health and hope to be back up and running soon!",
})}
</Text>
</Box>
</>
);
}

View file

@ -1,4 +1,3 @@
export * from "./Alert";
export * from "./Avatar";
export * from "./AvatarList";
export * from "./Badge";
@ -10,11 +9,11 @@ export * from "./Footer";
export * from "./Loader";
export * from "./Loading";
export * from "./LocalePicker";
export * from "./Navigation";
export * from "./NavMenu";
export * from "./Nav";
export * from "./Router";
export * from "./SinglePage";
export * from "./SiteWrapper";
export * from "./SuspenseLoader";
export * from "./Table";
export * from "./ThemeSwitcher";
export * from "./Unhealthy";

View file

@ -80,6 +80,12 @@
"setup-required": {
"defaultMessage": "Setup Required"
},
"unhealthy.title": {
"defaultMessage": "Nginx Proxy Manager is unhealthy"
},
"unhealthy.body": {
"defaultMessage": "We'll continue to check the health and hope to be back up and running soon!"
},
"user.email": {
"defaultMessage": "Email"
},

View file

@ -12,11 +12,10 @@ import {
useToast,
Link,
} from "@chakra-ui/react";
import { LocalePicker } from "components";
import { LocalePicker, ThemeSwitcher } from "components";
import { useAuthState } from "context";
import { intl } from "locale";
import { ThemeSwitcher } from "../../components/ThemeSwitcher";
import logo from "../../img/logo-256.png";
function Login() {

View file

@ -11,11 +11,10 @@ import {
useToast,
} from "@chakra-ui/react";
import { createUser } from "api/npm";
import { LocalePicker } from "components";
import { LocalePicker, ThemeSwitcher } from "components";
import { useAuthState, useHealthState } from "context";
import { intl } from "locale";
import { ThemeSwitcher } from "../../components/ThemeSwitcher";
import logo from "../../img/logo-256.png";
function Setup() {

View file

@ -7,7 +7,13 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "$DIR/../.common.sh"
docker_cmd() {
docker run --rm -e CI=true -v "$(pwd)/frontend:/app/frontend" -w /app/frontend node:14 ${*}
docker run --rm \
-e CI=true \
-e REACT_APP_VERSION=${BUILD_VERSION:-0.0.0} \
-e REACT_APP_COMMIT=${BUILD_COMMIT:-0000000} \
-v "$(pwd)/frontend:/app/frontend" \
-w /app/frontend node:14 \
${*}
}
cd "${DIR}/../.." || exit 1