mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-08-28 11:40:04 +00:00
Theme toggle, updated flag icons, custom font and some login page tweaks
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
"@typescript-eslint/parser": "^4.28.1",
|
"@typescript-eslint/parser": "^4.28.1",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
|
"country-flag-icons": "^1.4.11",
|
||||||
"date-fns": "2.22.1",
|
"date-fns": "2.22.1",
|
||||||
"eslint": "^7.30.0",
|
"eslint": "^7.30.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
@@ -46,6 +47,7 @@
|
|||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-async": "10.0.1",
|
"react-async": "10.0.1",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
|
"react-icons": "^4.3.1",
|
||||||
"react-intl": "^5.20.6",
|
"react-intl": "^5.20.6",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scripts": "4.0.3",
|
"react-scripts": "4.0.3",
|
||||||
@@ -97,6 +99,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@formatjs/cli": "^4.2.29"
|
"@formatjs/cli": "^4.2.29",
|
||||||
|
"@types/country-flag-icons": "^1.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -47,10 +47,6 @@
|
|||||||
content="/images/favicon/browserconfig.xml"
|
content="/images/favicon/browserconfig.xml"
|
||||||
/>
|
/>
|
||||||
<meta name="theme-color" content="#ffffff" />
|
<meta name="theme-color" content="#ffffff" />
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://unpkg.com/@tabler/core@1.0.0-beta3/dist/css/tabler-flags.min.css"
|
|
||||||
/>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
@@ -6,11 +6,13 @@ import { AuthProvider, HealthProvider, LocaleProvider } from "context";
|
|||||||
import { intl } from "locale";
|
import { intl } from "locale";
|
||||||
import { RawIntlProvider } from "react-intl";
|
import { RawIntlProvider } from "react-intl";
|
||||||
|
|
||||||
|
import lightTheme from "./theme/customTheme";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<RawIntlProvider value={intl}>
|
<RawIntlProvider value={intl}>
|
||||||
<LocaleProvider>
|
<LocaleProvider>
|
||||||
<ChakraProvider>
|
<ChakraProvider theme={lightTheme}>
|
||||||
<HealthProvider>
|
<HealthProvider>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<Router />
|
<Router />
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
import cn from "classnames";
|
import { Box } from "@chakra-ui/layout";
|
||||||
|
import { hasFlag } from "country-flag-icons";
|
||||||
|
// @ts-ignore Creating a typing for a subfolder is not easily possible
|
||||||
|
import Flags from "country-flag-icons/react/3x2";
|
||||||
|
|
||||||
export interface FlagProps {
|
export interface FlagProps {
|
||||||
/**
|
/**
|
||||||
@@ -8,21 +11,21 @@ export interface FlagProps {
|
|||||||
*/
|
*/
|
||||||
className?: string;
|
className?: string;
|
||||||
/**
|
/**
|
||||||
* Country code of flag
|
* Two letter country code of flag
|
||||||
*/
|
*/
|
||||||
country: string;
|
countryCode: string;
|
||||||
/**
|
|
||||||
* Size of the flag
|
|
||||||
*/
|
|
||||||
size?: string;
|
|
||||||
}
|
}
|
||||||
export const Flag: React.FC<FlagProps> = ({ className, country, size }) => {
|
export const Flag: React.FC<FlagProps> = ({ className, countryCode }) => {
|
||||||
const classes = [
|
countryCode = countryCode.toUpperCase();
|
||||||
`flag-country-${country.toLowerCase()}`,
|
|
||||||
{
|
|
||||||
[`flag-${size}`]: !!size,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return <span className={cn("flag", classes, className)} />;
|
if (hasFlag(countryCode)) {
|
||||||
|
const FlagElement = Flags[countryCode];
|
||||||
|
return (
|
||||||
|
<Box as={FlagElement} title={countryCode} className={className} w={6} />
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.error(`No flag for country ${countryCode} found!`);
|
||||||
|
|
||||||
|
return <Box w={6} h={4} />;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@@ -45,13 +45,13 @@ export const LocalePicker: React.FC<LocalPickerProps> = ({
|
|||||||
<Box className={className}>
|
<Box className={className}>
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton as={Button}>
|
<MenuButton as={Button}>
|
||||||
<Flag country={getFlagCodeForLocale(locale)} />
|
<Flag countryCode={getFlagCodeForLocale(locale)} />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList>
|
<MenuList>
|
||||||
{options.map((item) => {
|
{options.map((item) => {
|
||||||
return (
|
return (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
icon={<Flag country={item[0]} />}
|
icon={<Flag countryCode={getFlagCodeForLocale(item[0])} />}
|
||||||
onClick={() => changeTo(item[0])}
|
onClick={() => changeTo(item[0])}
|
||||||
rel={item[1]}
|
rel={item[1]}
|
||||||
key={`locale-${item[0]}`}>
|
key={`locale-${item[0]}`}>
|
||||||
|
13
frontend/src/components/ThemeSwitcher.tsx
Normal file
13
frontend/src/components/ThemeSwitcher.tsx
Normal file
@@ -0,0 +1,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 = () => {
|
||||||
|
const { colorMode, toggleColorMode } = useColorMode();
|
||||||
|
return (
|
||||||
|
<Button onClick={toggleColorMode}>
|
||||||
|
{colorMode === "light" ? <Icon as={FiMoon} /> : <Icon as={FiSun} />}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
};
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 163 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Before Width: | Height: | Size: 342 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,3 +1,5 @@
|
|||||||
|
@import "styles/fonts.scss";
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
#root {
|
#root {
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
|
import { ColorModeScript } from "@chakra-ui/react";
|
||||||
import * as ReactDOM from "react-dom";
|
import * as ReactDOM from "react-dom";
|
||||||
|
|
||||||
import App from "./App";
|
import App from "./App";
|
||||||
|
|
||||||
import "./index.scss";
|
import "./index.scss";
|
||||||
|
import customTheme from "./theme/customTheme";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Function {
|
interface Function {
|
||||||
@@ -18,4 +19,10 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactDOM.render(<App />, document.getElementById("root"));
|
ReactDOM.render(
|
||||||
|
<>
|
||||||
|
<ColorModeScript initialColorMode={customTheme.config.initialColorMode} />
|
||||||
|
<App />
|
||||||
|
</>,
|
||||||
|
document.getElementById("root"),
|
||||||
|
);
|
||||||
|
@@ -21,12 +21,12 @@ export const getFlagCodeForLocale = (locale?: string) => {
|
|||||||
switch (locale) {
|
switch (locale) {
|
||||||
case "de-DE":
|
case "de-DE":
|
||||||
case "de":
|
case "de":
|
||||||
return "de";
|
return "DE";
|
||||||
case "fa-IR":
|
case "fa-IR":
|
||||||
case "fa":
|
case "fa":
|
||||||
return "ir";
|
return "IR";
|
||||||
default:
|
default:
|
||||||
return "us";
|
return "US";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -10,11 +10,13 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
useColorModeValue,
|
useColorModeValue,
|
||||||
useToast,
|
useToast,
|
||||||
|
Link,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { LocalePicker } from "components";
|
import { LocalePicker } 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() {
|
||||||
@@ -58,23 +60,28 @@ function Login() {
|
|||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
minH={"100vh"}
|
minH={"100vh"}
|
||||||
align={"center"}
|
w={"100vw"}
|
||||||
justify={"center"}
|
flexDir={"column"}
|
||||||
bg={useColorModeValue("gray.50", "gray.800")}>
|
bg={useColorModeValue("gray.50", "gray.800")}>
|
||||||
<Stack spacing={8} mx={"auto"} maxW={"lg"} py={12} px={6}>
|
<Stack h={10} m={4} justify={"end"} direction={"row"}>
|
||||||
<Stack align={"center"}>
|
<ThemeSwitcher />
|
||||||
<img src={logo} width={100} alt="Logo" />
|
<LocalePicker className="text-right" />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
|
<Flex align={"center"} justify={"center"} flex={"1"}>
|
||||||
|
<Stack spacing={8} mx={"auto"} maxW={"md"} w={"full"} py={4} px={6}>
|
||||||
|
<Box align={"center"}>
|
||||||
|
<img src={logo} width={100} alt="Logo" />
|
||||||
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
rounded={"lg"}
|
rounded={"lg"}
|
||||||
bg={useColorModeValue("white", "gray.700")}
|
bg={useColorModeValue("white", "gray.700")}
|
||||||
boxShadow={"lg"}
|
boxShadow={"lg"}
|
||||||
p={8}>
|
p={8}>
|
||||||
<LocalePicker className="text-right" />
|
|
||||||
<Stack spacing={4}>
|
|
||||||
<form onSubmit={onSubmit}>
|
<form onSubmit={onSubmit}>
|
||||||
|
<Stack spacing={4}>
|
||||||
<FormControl id="email">
|
<FormControl id="email">
|
||||||
<FormLabel>
|
<FormLabel fontWeight={"bold"}>
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
id: "user.email",
|
id: "user.email",
|
||||||
defaultMessage: "Email",
|
defaultMessage: "Email",
|
||||||
@@ -96,7 +103,7 @@ function Login() {
|
|||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormControl id="password">
|
<FormControl id="password">
|
||||||
<FormLabel>
|
<FormLabel fontWeight={"bold"}>
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
id: "user.password",
|
id: "user.password",
|
||||||
defaultMessage: "Password",
|
defaultMessage: "Password",
|
||||||
@@ -118,11 +125,9 @@ function Login() {
|
|||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Stack spacing={10}>
|
<Stack spacing={10}>
|
||||||
<Stack
|
<Box textAlign={"end"}>
|
||||||
direction={{ base: "column", sm: "row" }}
|
<Link color={"blue.400"}>Forgot password?</Link>
|
||||||
align={"start"}
|
</Box>
|
||||||
justify={"space-between"}
|
|
||||||
/>
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
@@ -137,11 +142,13 @@ function Login() {
|
|||||||
})}
|
})}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</form>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</form>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
<Box h={10} m={4} />
|
||||||
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
47
frontend/src/styles/fonts.scss
Normal file
47
frontend/src/styles/fonts.scss
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/* source-sans-pro-regular - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Source Sans Pro";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local(""),
|
||||||
|
url("../fonts/source-sans-pro/source-sans-pro-v14-latin-regular.woff2")
|
||||||
|
format("woff2"),
|
||||||
|
url("../fonts/source-sans-pro/source-sans-pro-v14-latin-regular.woff")
|
||||||
|
format("woff");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* source-sans-pro-italic - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Source Sans Pro";
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
src: local(""),
|
||||||
|
url("../fonts/source-sans-pro/source-sans-pro-v14-latin-italic.woff2")
|
||||||
|
format("woff2"),
|
||||||
|
url("../fonts/source-sans-pro/source-sans-pro-v14-latin-italic.woff")
|
||||||
|
format("woff");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* source-sans-pro-700 - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Source Sans Pro";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
src: local(""),
|
||||||
|
url("../fonts/source-sans-pro/source-sans-pro-v14-latin-700.woff2")
|
||||||
|
format("woff2"),
|
||||||
|
url("../fonts/source-sans-pro/source-sans-pro-v14-latin-700.woff")
|
||||||
|
format("woff");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* source-sans-pro-700italic - latin */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Source Sans Pro";
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 700;
|
||||||
|
src: local(""),
|
||||||
|
url("../fonts/source-sans-pro/source-sans-pro-v14-latin-700italic.woff2")
|
||||||
|
format("woff2"),
|
||||||
|
url("../fonts/source-sans-pro/source-sans-pro-v14-latin-700italic.woff")
|
||||||
|
format("woff");
|
||||||
|
}
|
16
frontend/src/theme/customTheme.ts
Normal file
16
frontend/src/theme/customTheme.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { theme as chakraTheme, ThemeConfig } from "@chakra-ui/react";
|
||||||
|
import { extendTheme } from "@chakra-ui/react";
|
||||||
|
|
||||||
|
// declare a variable for fonts and set our fonts
|
||||||
|
const fonts = {
|
||||||
|
...chakraTheme.fonts,
|
||||||
|
body: `"Source Sans Pro",-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"`,
|
||||||
|
heading: `"Source Sans Pro",-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol"`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const config: ThemeConfig = {
|
||||||
|
initialColorMode: "system",
|
||||||
|
};
|
||||||
|
|
||||||
|
const lightTheme = extendTheme({ fonts, config });
|
||||||
|
export default lightTheme;
|
Reference in New Issue
Block a user