mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-08-28 03:30:05 +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",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"classnames": "^2.3.1",
|
||||
"country-flag-icons": "^1.4.11",
|
||||
"date-fns": "2.22.1",
|
||||
"eslint": "^7.30.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
@@ -46,6 +47,7 @@
|
||||
"react": "^17.0.2",
|
||||
"react-async": "10.0.1",
|
||||
"react-dom": "17.0.2",
|
||||
"react-icons": "^4.3.1",
|
||||
"react-intl": "^5.20.6",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "4.0.3",
|
||||
@@ -97,6 +99,7 @@
|
||||
}
|
||||
},
|
||||
"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"
|
||||
/>
|
||||
<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>
|
||||
<body>
|
||||
<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 { RawIntlProvider } from "react-intl";
|
||||
|
||||
import lightTheme from "./theme/customTheme";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<RawIntlProvider value={intl}>
|
||||
<LocaleProvider>
|
||||
<ChakraProvider>
|
||||
<ChakraProvider theme={lightTheme}>
|
||||
<HealthProvider>
|
||||
<AuthProvider>
|
||||
<Router />
|
||||
|
@@ -1,6 +1,9 @@
|
||||
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 {
|
||||
/**
|
||||
@@ -8,21 +11,21 @@ export interface FlagProps {
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* Country code of flag
|
||||
* Two letter country code of flag
|
||||
*/
|
||||
country: string;
|
||||
/**
|
||||
* Size of the flag
|
||||
*/
|
||||
size?: string;
|
||||
countryCode: string;
|
||||
}
|
||||
export const Flag: React.FC<FlagProps> = ({ className, country, size }) => {
|
||||
const classes = [
|
||||
`flag-country-${country.toLowerCase()}`,
|
||||
{
|
||||
[`flag-${size}`]: !!size,
|
||||
},
|
||||
];
|
||||
export const Flag: React.FC<FlagProps> = ({ className, countryCode }) => {
|
||||
countryCode = countryCode.toUpperCase();
|
||||
|
||||
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}>
|
||||
<Menu>
|
||||
<MenuButton as={Button}>
|
||||
<Flag country={getFlagCodeForLocale(locale)} />
|
||||
<Flag countryCode={getFlagCodeForLocale(locale)} />
|
||||
</MenuButton>
|
||||
<MenuList>
|
||||
{options.map((item) => {
|
||||
return (
|
||||
<MenuItem
|
||||
icon={<Flag country={item[0]} />}
|
||||
icon={<Flag countryCode={getFlagCodeForLocale(item[0])} />}
|
||||
onClick={() => changeTo(item[0])}
|
||||
rel={item[1]}
|
||||
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,
|
||||
body,
|
||||
#root {
|
||||
|
@@ -1,10 +1,11 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { ColorModeScript } from "@chakra-ui/react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
|
||||
import App from "./App";
|
||||
|
||||
import "./index.scss";
|
||||
import customTheme from "./theme/customTheme";
|
||||
|
||||
declare global {
|
||||
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) {
|
||||
case "de-DE":
|
||||
case "de":
|
||||
return "de";
|
||||
return "DE";
|
||||
case "fa-IR":
|
||||
case "fa":
|
||||
return "ir";
|
||||
return "IR";
|
||||
default:
|
||||
return "us";
|
||||
return "US";
|
||||
}
|
||||
};
|
||||
|
||||
|
@@ -10,11 +10,13 @@ import {
|
||||
Button,
|
||||
useColorModeValue,
|
||||
useToast,
|
||||
Link,
|
||||
} from "@chakra-ui/react";
|
||||
import { LocalePicker } from "components";
|
||||
import { useAuthState } from "context";
|
||||
import { intl } from "locale";
|
||||
|
||||
import { ThemeSwitcher } from "../../components/ThemeSwitcher";
|
||||
import logo from "../../img/logo-256.png";
|
||||
|
||||
function Login() {
|
||||
@@ -58,89 +60,94 @@ function Login() {
|
||||
return (
|
||||
<Flex
|
||||
minH={"100vh"}
|
||||
align={"center"}
|
||||
justify={"center"}
|
||||
w={"100vw"}
|
||||
flexDir={"column"}
|
||||
bg={useColorModeValue("gray.50", "gray.800")}>
|
||||
<Stack spacing={8} mx={"auto"} maxW={"lg"} py={12} px={6}>
|
||||
<Stack align={"center"}>
|
||||
<img src={logo} width={100} alt="Logo" />
|
||||
</Stack>
|
||||
<Box
|
||||
rounded={"lg"}
|
||||
bg={useColorModeValue("white", "gray.700")}
|
||||
boxShadow={"lg"}
|
||||
p={8}>
|
||||
<LocalePicker className="text-right" />
|
||||
<Stack spacing={4}>
|
||||
<Stack h={10} m={4} justify={"end"} direction={"row"}>
|
||||
<ThemeSwitcher />
|
||||
<LocalePicker className="text-right" />
|
||||
</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
|
||||
rounded={"lg"}
|
||||
bg={useColorModeValue("white", "gray.700")}
|
||||
boxShadow={"lg"}
|
||||
p={8}>
|
||||
<form onSubmit={onSubmit}>
|
||||
<FormControl id="email">
|
||||
<FormLabel>
|
||||
{intl.formatMessage({
|
||||
id: "user.email",
|
||||
defaultMessage: "Email",
|
||||
})}
|
||||
</FormLabel>
|
||||
<Input
|
||||
ref={emailRef}
|
||||
type="email"
|
||||
onChange={onChange}
|
||||
name="email"
|
||||
value={formData.email}
|
||||
disabled={loading}
|
||||
placeholder={intl.formatMessage({
|
||||
id: "user.email",
|
||||
defaultMessage: "Email",
|
||||
})}
|
||||
maxLength={150}
|
||||
required
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl id="password">
|
||||
<FormLabel>
|
||||
{intl.formatMessage({
|
||||
id: "user.password",
|
||||
defaultMessage: "Password",
|
||||
})}
|
||||
</FormLabel>
|
||||
<Input
|
||||
type="password"
|
||||
onChange={onChange}
|
||||
name="password"
|
||||
value={formData.password}
|
||||
disabled={loading}
|
||||
placeholder={intl.formatMessage({
|
||||
id: "user.password",
|
||||
defaultMessage: "Password",
|
||||
})}
|
||||
maxLength={100}
|
||||
autoComplete="off"
|
||||
required
|
||||
/>
|
||||
</FormControl>
|
||||
<Stack spacing={10}>
|
||||
<Stack
|
||||
direction={{ base: "column", sm: "row" }}
|
||||
align={"start"}
|
||||
justify={"space-between"}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
loading={loading}
|
||||
bg={"blue.400"}
|
||||
color={"white"}
|
||||
_hover={{
|
||||
bg: "blue.500",
|
||||
}}>
|
||||
{intl.formatMessage({
|
||||
id: "login.login",
|
||||
defaultMessage: "Sign in",
|
||||
})}
|
||||
</Button>
|
||||
<Stack spacing={4}>
|
||||
<FormControl id="email">
|
||||
<FormLabel fontWeight={"bold"}>
|
||||
{intl.formatMessage({
|
||||
id: "user.email",
|
||||
defaultMessage: "Email",
|
||||
})}
|
||||
</FormLabel>
|
||||
<Input
|
||||
ref={emailRef}
|
||||
type="email"
|
||||
onChange={onChange}
|
||||
name="email"
|
||||
value={formData.email}
|
||||
disabled={loading}
|
||||
placeholder={intl.formatMessage({
|
||||
id: "user.email",
|
||||
defaultMessage: "Email",
|
||||
})}
|
||||
maxLength={150}
|
||||
required
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl id="password">
|
||||
<FormLabel fontWeight={"bold"}>
|
||||
{intl.formatMessage({
|
||||
id: "user.password",
|
||||
defaultMessage: "Password",
|
||||
})}
|
||||
</FormLabel>
|
||||
<Input
|
||||
type="password"
|
||||
onChange={onChange}
|
||||
name="password"
|
||||
value={formData.password}
|
||||
disabled={loading}
|
||||
placeholder={intl.formatMessage({
|
||||
id: "user.password",
|
||||
defaultMessage: "Password",
|
||||
})}
|
||||
maxLength={100}
|
||||
autoComplete="off"
|
||||
required
|
||||
/>
|
||||
</FormControl>
|
||||
<Stack spacing={10}>
|
||||
<Box textAlign={"end"}>
|
||||
<Link color={"blue.400"}>Forgot password?</Link>
|
||||
</Box>
|
||||
<Button
|
||||
type="submit"
|
||||
loading={loading}
|
||||
bg={"blue.400"}
|
||||
color={"white"}
|
||||
_hover={{
|
||||
bg: "blue.500",
|
||||
}}>
|
||||
{intl.formatMessage({
|
||||
id: "login.login",
|
||||
defaultMessage: "Sign in",
|
||||
})}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</form>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
</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