WIP Chakra site wrapper
This commit is contained in:
parent
8a6a355097
commit
eff9c45ea8
22 changed files with 262 additions and 876 deletions
|
@ -1,2 +1,4 @@
|
||||||
PORT=9000
|
PORT=9000
|
||||||
IMAGE_INLINE_SIZE_LIMIT=20000
|
IMAGE_INLINE_SIZE_LIMIT=20000
|
||||||
|
REACT_APP_VERSION=development
|
||||||
|
REACT_APP_COMMIT=abcdef1
|
|
@ -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;
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1 +0,0 @@
|
||||||
export * from "./Alert";
|
|
|
@ -1,18 +1,37 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { useHealthState } from "context";
|
import {
|
||||||
|
Box,
|
||||||
|
Container,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
useColorModeValue,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
import { intl } from "locale";
|
import { intl } from "locale";
|
||||||
|
|
||||||
function Footer() {
|
function Footer() {
|
||||||
const { health } = useHealthState();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="footer footer-transparent d-print-none">
|
<Box
|
||||||
<div className="container">
|
bg={useColorModeValue("gray.50", "gray.900")}
|
||||||
<div className="row text-center align-items-center flex-row-reverse">
|
color={useColorModeValue("gray.700", "gray.200")}>
|
||||||
<div className="col-lg-auto ms-lg-auto">
|
<Container
|
||||||
<ul className="list-inline list-inline-dots mb-0">
|
as={Stack}
|
||||||
<li className="list-inline-item">
|
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
|
<a
|
||||||
href="https://nginxproxymanager.com?utm_source=npm"
|
href="https://nginxproxymanager.com?utm_source=npm"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
@ -23,8 +42,6 @@ function Footer() {
|
||||||
defaultMessage: "User Guide",
|
defaultMessage: "User Guide",
|
||||||
})}
|
})}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
|
||||||
<li className="list-inline-item">
|
|
||||||
<a
|
<a
|
||||||
href="https://github.com/jc21/nginx-proxy-manager/releases?utm_source=npm"
|
href="https://github.com/jc21/nginx-proxy-manager/releases?utm_source=npm"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
@ -35,8 +52,6 @@ function Footer() {
|
||||||
defaultMessage: "Change Log",
|
defaultMessage: "Change Log",
|
||||||
})}
|
})}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
|
||||||
<li className="list-inline-item">
|
|
||||||
<a
|
<a
|
||||||
href="https://github.com/jc21/nginx-proxy-manager?utm_source=npm"
|
href="https://github.com/jc21/nginx-proxy-manager?utm_source=npm"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
@ -47,34 +62,16 @@ function Footer() {
|
||||||
defaultMessage: "Github",
|
defaultMessage: "Github",
|
||||||
})}
|
})}
|
||||||
</a>
|
</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
|
<a
|
||||||
href="https://github.com/jc21/nginx-proxy-manager/releases?utm_source=npm"
|
href="https://github.com/jc21/nginx-proxy-manager/releases?utm_source=npm"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
className="link-secondary"
|
|
||||||
rel="noopener noreferrer">
|
rel="noopener noreferrer">
|
||||||
v{health.version} {String.fromCharCode(183)} {health.commit}
|
v{process.env.REACT_APP_VERSION} {String.fromCharCode(183)}{" "}
|
||||||
|
{process.env.REACT_APP_COMMIT}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</Stack>
|
||||||
</ul>
|
</Container>
|
||||||
</div>
|
</Box>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
99
frontend/src/components/Nav/Nav.tsx
Normal file
99
frontend/src/components/Nav/Nav.tsx
Normal 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;
|
25
frontend/src/components/Nav/NavLink.tsx
Normal file
25
frontend/src/components/Nav/NavLink.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
1
frontend/src/components/Nav/index.ts
Normal file
1
frontend/src/components/Nav/index.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export * from "./Nav";
|
|
@ -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 };
|
|
|
@ -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;
|
|
|
@ -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} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -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;
|
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1 +0,0 @@
|
||||||
export * from "./Navigation";
|
|
|
@ -1,10 +1,10 @@
|
||||||
import React, { ReactNode } from "react";
|
import React, { ReactNode } from "react";
|
||||||
|
|
||||||
import { Footer } from "components";
|
import { Footer } from "components";
|
||||||
import { Avatar, Dropdown, Navigation } from "components";
|
import { Nav } from "components";
|
||||||
import { LocalePicker } from "components";
|
// import { LocalePicker } from "components";
|
||||||
import { useAuthState, useUserState } from "context";
|
// import { useAuthState, useUserState } from "context";
|
||||||
import { intl } from "locale";
|
// import { intl } from "locale";
|
||||||
import styled from "styled-components";
|
import styled from "styled-components";
|
||||||
|
|
||||||
const StyledSiteContainer = styled.div`
|
const StyledSiteContainer = styled.div`
|
||||||
|
@ -29,13 +29,14 @@ interface Props {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}
|
}
|
||||||
function SiteWrapper({ children }: Props) {
|
function SiteWrapper({ children }: Props) {
|
||||||
const user = useUserState();
|
// const user = useUserState();
|
||||||
const { logout } = useAuthState();
|
// const { logout } = useAuthState();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledSiteContainer className="wrapper">
|
<StyledSiteContainer className="wrapper">
|
||||||
<StyledScrollContainer>
|
<StyledScrollContainer>
|
||||||
<Navigation.Header
|
<Nav />
|
||||||
|
{/*
|
||||||
theme="light"
|
theme="light"
|
||||||
brandContent={
|
brandContent={
|
||||||
<img
|
<img
|
||||||
|
@ -75,6 +76,7 @@ function SiteWrapper({ children }: Props) {
|
||||||
</Dropdown.Item>,
|
</Dropdown.Item>,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
*/}
|
||||||
<div className="content">
|
<div className="content">
|
||||||
<div className="container-xl">
|
<div className="container-xl">
|
||||||
<StyledContentContainer>{children}</StyledContentContainer>
|
<StyledContentContainer>{children}</StyledContentContainer>
|
||||||
|
|
|
@ -3,11 +3,13 @@ import React from "react";
|
||||||
import { Button, Icon, useColorMode } from "@chakra-ui/react";
|
import { Button, Icon, useColorMode } from "@chakra-ui/react";
|
||||||
import { FiSun, FiMoon } from "react-icons/fi";
|
import { FiSun, FiMoon } from "react-icons/fi";
|
||||||
|
|
||||||
export const ThemeSwitcher: React.FC = () => {
|
function ThemeSwitcher() {
|
||||||
const { colorMode, toggleColorMode } = useColorMode();
|
const { colorMode, toggleColorMode } = useColorMode();
|
||||||
return (
|
return (
|
||||||
<Button onClick={toggleColorMode}>
|
<Button onClick={toggleColorMode}>
|
||||||
{colorMode === "light" ? <Icon as={FiMoon} /> : <Icon as={FiSun} />}
|
{colorMode === "light" ? <Icon as={FiMoon} /> : <Icon as={FiSun} />}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export { ThemeSwitcher };
|
||||||
|
|
|
@ -1,44 +1,45 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { Alert } from "components";
|
import { Box, Flex, Heading, Text, Stack } from "@chakra-ui/react";
|
||||||
import styled from "styled-components";
|
import { LocalePicker } from "components";
|
||||||
|
import { intl } from "locale";
|
||||||
const Root = styled.div`
|
import { FaTimes } from "react-icons/fa";
|
||||||
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;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
function Unhealthy() {
|
function Unhealthy() {
|
||||||
return (
|
return (
|
||||||
<Root>
|
<>
|
||||||
<Alert type="warning" icon="alert-triangle">
|
<Stack h={10} m={4} justify={"end"} direction={"row"}>
|
||||||
Nginx Proxy Manager is <strong>unhealthy</strong>. We'll continue to
|
<LocalePicker className="text-right" />
|
||||||
check the health and hope to be back up and running soon!
|
</Stack>
|
||||||
</Alert>
|
<Box textAlign="center" py={10} px={6}>
|
||||||
</Root>
|
<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>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
export * from "./Alert";
|
|
||||||
export * from "./Avatar";
|
export * from "./Avatar";
|
||||||
export * from "./AvatarList";
|
export * from "./AvatarList";
|
||||||
export * from "./Badge";
|
export * from "./Badge";
|
||||||
|
@ -10,11 +9,11 @@ export * from "./Footer";
|
||||||
export * from "./Loader";
|
export * from "./Loader";
|
||||||
export * from "./Loading";
|
export * from "./Loading";
|
||||||
export * from "./LocalePicker";
|
export * from "./LocalePicker";
|
||||||
export * from "./Navigation";
|
export * from "./Nav";
|
||||||
export * from "./NavMenu";
|
|
||||||
export * from "./Router";
|
export * from "./Router";
|
||||||
export * from "./SinglePage";
|
export * from "./SinglePage";
|
||||||
export * from "./SiteWrapper";
|
export * from "./SiteWrapper";
|
||||||
export * from "./SuspenseLoader";
|
export * from "./SuspenseLoader";
|
||||||
export * from "./Table";
|
export * from "./Table";
|
||||||
|
export * from "./ThemeSwitcher";
|
||||||
export * from "./Unhealthy";
|
export * from "./Unhealthy";
|
||||||
|
|
|
@ -80,6 +80,12 @@
|
||||||
"setup-required": {
|
"setup-required": {
|
||||||
"defaultMessage": "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": {
|
"user.email": {
|
||||||
"defaultMessage": "Email"
|
"defaultMessage": "Email"
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,11 +12,10 @@ import {
|
||||||
useToast,
|
useToast,
|
||||||
Link,
|
Link,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { LocalePicker } from "components";
|
import { LocalePicker, ThemeSwitcher } from "components";
|
||||||
import { useAuthState } from "context";
|
import { useAuthState } from "context";
|
||||||
import { intl } from "locale";
|
import { intl } from "locale";
|
||||||
|
|
||||||
import { ThemeSwitcher } from "../../components/ThemeSwitcher";
|
|
||||||
import logo from "../../img/logo-256.png";
|
import logo from "../../img/logo-256.png";
|
||||||
|
|
||||||
function Login() {
|
function Login() {
|
||||||
|
|
|
@ -11,11 +11,10 @@ import {
|
||||||
useToast,
|
useToast,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { createUser } from "api/npm";
|
import { createUser } from "api/npm";
|
||||||
import { LocalePicker } from "components";
|
import { LocalePicker, ThemeSwitcher } from "components";
|
||||||
import { useAuthState, useHealthState } from "context";
|
import { useAuthState, useHealthState } from "context";
|
||||||
import { intl } from "locale";
|
import { intl } from "locale";
|
||||||
|
|
||||||
import { ThemeSwitcher } from "../../components/ThemeSwitcher";
|
|
||||||
import logo from "../../img/logo-256.png";
|
import logo from "../../img/logo-256.png";
|
||||||
|
|
||||||
function Setup() {
|
function Setup() {
|
||||||
|
|
|
@ -7,7 +7,13 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
. "$DIR/../.common.sh"
|
. "$DIR/../.common.sh"
|
||||||
|
|
||||||
docker_cmd() {
|
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
|
cd "${DIR}/../.." || exit 1
|
||||||
|
|
Loading…
Reference in a new issue