mirror of
https://github.com/NginxProxyManager/nginx-proxy-manager.git
synced 2025-08-28 03:30:05 +00:00
Signup page conversion to chakra
This commit is contained in:
@@ -3,6 +3,9 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@chakra-ui/react": "^1.6.12",
|
||||||
|
"@emotion/react": "^11",
|
||||||
|
"@emotion/styled": "^11",
|
||||||
"@testing-library/jest-dom": "5.14.1",
|
"@testing-library/jest-dom": "5.14.1",
|
||||||
"@testing-library/react": "12.0.0",
|
"@testing-library/react": "12.0.0",
|
||||||
"@types/humps": "^2.0.0",
|
"@types/humps": "^2.0.0",
|
||||||
@@ -28,6 +31,7 @@
|
|||||||
"eslint-plugin-prettier": "^3.4.0",
|
"eslint-plugin-prettier": "^3.4.0",
|
||||||
"eslint-plugin-react": "^7.24.0",
|
"eslint-plugin-react": "^7.24.0",
|
||||||
"eslint-plugin-react-hooks": "^4.2.0",
|
"eslint-plugin-react-hooks": "^4.2.0",
|
||||||
|
"framer-motion": "^4",
|
||||||
"humps": "^2.0.1",
|
"humps": "^2.0.1",
|
||||||
"jest-date-mock": "1.0.8",
|
"jest-date-mock": "1.0.8",
|
||||||
"jest-fetch-mock": "3.0.3",
|
"jest-fetch-mock": "3.0.3",
|
||||||
|
@@ -47,14 +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://fonts.googleapis.com/css?family=Source+Sans+Pro:300,300i,400,400i,500,500i,600,600i,700,700i&subset=latin-ext"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="stylesheet"
|
|
||||||
href="https://unpkg.com/@tabler/core@1.0.0-beta3/dist/css/tabler.min.css"
|
|
||||||
/>
|
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://unpkg.com/@tabler/core@1.0.0-beta3/dist/css/tabler-flags.min.css"
|
href="https://unpkg.com/@tabler/core@1.0.0-beta3/dist/css/tabler-flags.min.css"
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
import { ChakraProvider } from "@chakra-ui/react";
|
||||||
import Router from "components/Router";
|
import Router from "components/Router";
|
||||||
import { AuthProvider, HealthProvider, LocaleProvider } from "context";
|
import { AuthProvider, HealthProvider, LocaleProvider } from "context";
|
||||||
import { intl } from "locale";
|
import { intl } from "locale";
|
||||||
@@ -9,11 +10,13 @@ function App() {
|
|||||||
return (
|
return (
|
||||||
<RawIntlProvider value={intl}>
|
<RawIntlProvider value={intl}>
|
||||||
<LocaleProvider>
|
<LocaleProvider>
|
||||||
<HealthProvider>
|
<ChakraProvider>
|
||||||
<AuthProvider>
|
<HealthProvider>
|
||||||
<Router />
|
<AuthProvider>
|
||||||
</AuthProvider>
|
<Router />
|
||||||
</HealthProvider>
|
</AuthProvider>
|
||||||
|
</HealthProvider>
|
||||||
|
</ChakraProvider>
|
||||||
</LocaleProvider>
|
</LocaleProvider>
|
||||||
</RawIntlProvider>
|
</RawIntlProvider>
|
||||||
);
|
);
|
||||||
|
@@ -1,82 +1,71 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React from "react";
|
||||||
|
|
||||||
import { Button, Dropdown, Flag } from "components";
|
import {
|
||||||
|
Button,
|
||||||
|
Box,
|
||||||
|
Menu,
|
||||||
|
MenuButton,
|
||||||
|
MenuList,
|
||||||
|
MenuItem,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
|
import { Flag } from "components";
|
||||||
import { useLocaleState } from "context";
|
import { useLocaleState } from "context";
|
||||||
import { changeLocale, getFlagCodeForLocale, intl } from "locale";
|
import { changeLocale, getFlagCodeForLocale, intl } from "locale";
|
||||||
|
|
||||||
export interface LocalPickerProps {
|
export interface LocalPickerProps {
|
||||||
/**
|
/**
|
||||||
* On click handler
|
* On change handler
|
||||||
*/
|
*/
|
||||||
onChange?: any;
|
onChange?: any;
|
||||||
|
/**
|
||||||
|
* Class
|
||||||
|
*/
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const LocalePicker: React.FC<LocalPickerProps> = ({
|
export const LocalePicker: React.FC<LocalPickerProps> = ({
|
||||||
onChange,
|
onChange,
|
||||||
...rest
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
const dropRef = useRef(null);
|
|
||||||
const { locale, setLocale } = useLocaleState();
|
const { locale, setLocale } = useLocaleState();
|
||||||
const [localeShown, setLocaleShown] = useState(false);
|
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
["us", "en-US"],
|
["us", "en-US"],
|
||||||
["de", "de-DE"],
|
["de", "de-DE"],
|
||||||
["ir", "fa-IR"],
|
["fa", "fa-IR"],
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleOnChange = (e: any) => {
|
const changeTo = (lang: string) => {
|
||||||
changeLocale(e.currentTarget.rel);
|
changeLocale(lang);
|
||||||
setLocale(e.currentTarget.rel);
|
setLocale(lang);
|
||||||
setLocaleShown(false);
|
|
||||||
onChange && onChange(locale);
|
onChange && onChange(locale);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClickOutside = (event: any) => {
|
|
||||||
if (
|
|
||||||
dropRef.current &&
|
|
||||||
// @ts-expect-error ts-migrate(2531) FIXME: Object is possibly 'null'.
|
|
||||||
!dropRef.current.contains(event.target)
|
|
||||||
) {
|
|
||||||
setLocaleShown(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.addEventListener("mousedown", handleClickOutside);
|
|
||||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dropdown" {...rest} ref={dropRef}>
|
<Box className={className}>
|
||||||
<Button
|
<Menu>
|
||||||
type="button"
|
<MenuButton as={Button}>
|
||||||
shape="ghost"
|
<Flag country={getFlagCodeForLocale(locale)} />
|
||||||
onClick={(e: any) => {
|
</MenuButton>
|
||||||
setLocaleShown(!localeShown);
|
<MenuList>
|
||||||
e.preventDefault();
|
{options.map((item) => {
|
||||||
}}
|
return (
|
||||||
iconOnly>
|
<MenuItem
|
||||||
<Flag country={getFlagCodeForLocale(locale)} />
|
icon={<Flag country={item[0]} />}
|
||||||
</Button>
|
onClick={() => changeTo(item[0])}
|
||||||
<Dropdown
|
rel={item[1]}
|
||||||
className="dropdown-menu-end dropdown-menu-card"
|
key={`locale-${item[0]}`}>
|
||||||
show={localeShown}>
|
<span>
|
||||||
{options.map((item) => {
|
{intl.formatMessage({
|
||||||
return (
|
id: `locale-${item[1]}`,
|
||||||
<Dropdown.Item
|
defaultMessage: item[1],
|
||||||
key={`locale-${item[0]}`}
|
})}
|
||||||
rel={item[1]}
|
</span>
|
||||||
icon={<Flag country={item[0]} />}
|
</MenuItem>
|
||||||
onClick={handleOnChange}>
|
);
|
||||||
{intl.formatMessage({
|
})}
|
||||||
id: `locale-${item[1]}`,
|
</MenuList>
|
||||||
defaultMessage: item[1],
|
</Menu>
|
||||||
})}
|
</Box>
|
||||||
</Dropdown.Item>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Dropdown>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
BIN
frontend/src/img/logo-256.png
Normal file
BIN
frontend/src/img/logo-256.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
@@ -17,59 +17,3 @@ body {
|
|||||||
.text-right {
|
.text-right {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide the dropdown arrow when it is inlined in the mobile menu
|
|
||||||
@media (max-width: 767.98px) {
|
|
||||||
.navbar-collapse .dropdown-menu-arrow::before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
.btn {
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-loading {
|
|
||||||
color: transparent !important;
|
|
||||||
pointer-events: none;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:after {
|
|
||||||
content: "";
|
|
||||||
-webkit-animation: loader 500ms infinite linear;
|
|
||||||
animation: loader 500ms infinite linear;
|
|
||||||
border: 2px solid #fff;
|
|
||||||
border-radius: 50%;
|
|
||||||
border-right-color: transparent !important;
|
|
||||||
border-top-color: transparent !important;
|
|
||||||
display: block;
|
|
||||||
height: 1.4em;
|
|
||||||
width: 1.4em;
|
|
||||||
position: absolute;
|
|
||||||
left: calc(50% - (1.4em / 2));
|
|
||||||
top: calc(50% - (1.4em / 2));
|
|
||||||
-webkit-transform-origin: center;
|
|
||||||
transform-origin: center;
|
|
||||||
position: absolute !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
background: #fff;
|
|
||||||
border-top: 1px solid rgba(0, 40, 100, 0.12);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
padding: 1.25rem 0;
|
|
||||||
color: #9aa0ac;
|
|
||||||
|
|
||||||
a:not(.btn) {
|
|
||||||
color: #6e7687;
|
|
||||||
text-decoration: none;
|
|
||||||
background-color: initial;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
@@ -1,17 +1,27 @@
|
|||||||
import React, { useEffect, useRef, useState, ChangeEvent } from "react";
|
import React, { useEffect, useRef, useState, ChangeEvent } from "react";
|
||||||
|
|
||||||
import { Alert, Button } from "components";
|
import {
|
||||||
|
Flex,
|
||||||
|
Box,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
Input,
|
||||||
|
Stack,
|
||||||
|
Button,
|
||||||
|
useColorModeValue,
|
||||||
|
useToast,
|
||||||
|
} 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 logo from "../../img/logo-text-vertical-grey.png";
|
import logo from "../../img/logo-256.png";
|
||||||
|
|
||||||
function Login() {
|
function Login() {
|
||||||
|
const toast = useToast();
|
||||||
const emailRef = useRef(null);
|
const emailRef = useRef(null);
|
||||||
const { login } = useAuthState();
|
const { login } = useAuthState();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [errorMessage, setErrorMessage] = useState("");
|
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
email: "",
|
email: "",
|
||||||
password: "",
|
password: "",
|
||||||
@@ -20,12 +30,18 @@ function Login() {
|
|||||||
const onSubmit = async (e: React.FormEvent) => {
|
const onSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setErrorMessage("");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await login(formData.email, formData.password);
|
await login(formData.email, formData.password);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setErrorMessage(err.message);
|
toast({
|
||||||
|
title: "Login Error",
|
||||||
|
description: err.message,
|
||||||
|
status: "error",
|
||||||
|
position: "top",
|
||||||
|
duration: 3000,
|
||||||
|
isClosable: true,
|
||||||
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -40,87 +56,92 @@ function Login() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container-tight py-4">
|
<Flex
|
||||||
<div className="text-center mb-4">
|
minH={"100vh"}
|
||||||
<img src={logo} alt="Logo" />
|
align={"center"}
|
||||||
</div>
|
justify={"center"}
|
||||||
<form
|
bg={useColorModeValue("gray.50", "gray.800")}>
|
||||||
className="card card-md"
|
<Stack spacing={8} mx={"auto"} maxW={"lg"} py={12} px={6}>
|
||||||
method="post"
|
<Stack align={"center"}>
|
||||||
autoComplete="off"
|
<img src={logo} width={100} alt="Logo" />
|
||||||
onSubmit={onSubmit}>
|
</Stack>
|
||||||
<div className="card-body">
|
<Box
|
||||||
<div className="row mb-4">
|
rounded={"lg"}
|
||||||
<div className="col" />
|
bg={useColorModeValue("white", "gray.700")}
|
||||||
<div className="col col-md-2">
|
boxShadow={"lg"}
|
||||||
<LocalePicker />
|
p={8}>
|
||||||
</div>
|
<LocalePicker className="text-right" />
|
||||||
</div>
|
<Stack spacing={4}>
|
||||||
{errorMessage ? <Alert type="danger">{errorMessage}</Alert> : null}
|
<form onSubmit={onSubmit}>
|
||||||
<div className="mb-3">
|
<FormControl id="email">
|
||||||
<label className="form-label">
|
<FormLabel>
|
||||||
{intl.formatMessage({
|
{intl.formatMessage({
|
||||||
id: "user.email",
|
id: "user.email",
|
||||||
defaultMessage: "Email",
|
defaultMessage: "Email",
|
||||||
})}
|
})}
|
||||||
</label>
|
</FormLabel>
|
||||||
<input
|
<Input
|
||||||
ref={emailRef}
|
ref={emailRef}
|
||||||
type="email"
|
type="email"
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
className="form-control"
|
name="email"
|
||||||
name="email"
|
value={formData.email}
|
||||||
value={formData.email}
|
disabled={loading}
|
||||||
disabled={loading}
|
placeholder={intl.formatMessage({
|
||||||
placeholder={intl.formatMessage({
|
id: "user.email",
|
||||||
id: "user.email",
|
defaultMessage: "Email",
|
||||||
defaultMessage: "Email",
|
})}
|
||||||
})}
|
maxLength={150}
|
||||||
maxLength={150}
|
required
|
||||||
required
|
/>
|
||||||
/>
|
</FormControl>
|
||||||
</div>
|
<FormControl id="password">
|
||||||
<div className="mb-2">
|
<FormLabel>
|
||||||
<label className="form-label">
|
{intl.formatMessage({
|
||||||
{intl.formatMessage({
|
id: "user.password",
|
||||||
id: "user.password",
|
defaultMessage: "Password",
|
||||||
defaultMessage: "Password",
|
})}
|
||||||
})}
|
</FormLabel>
|
||||||
</label>
|
<Input
|
||||||
<div className="input-group input-group-flat">
|
type="password"
|
||||||
<input
|
onChange={onChange}
|
||||||
type="password"
|
name="password"
|
||||||
onChange={onChange}
|
value={formData.password}
|
||||||
className="form-control"
|
disabled={loading}
|
||||||
name="password"
|
placeholder={intl.formatMessage({
|
||||||
value={formData.password}
|
id: "user.password",
|
||||||
disabled={loading}
|
defaultMessage: "Password",
|
||||||
placeholder={intl.formatMessage({
|
})}
|
||||||
id: "user.password",
|
maxLength={100}
|
||||||
defaultMessage: "Password",
|
autoComplete="off"
|
||||||
})}
|
required
|
||||||
minLength={8}
|
/>
|
||||||
maxLength={100}
|
</FormControl>
|
||||||
autoComplete="off"
|
<Stack spacing={10}>
|
||||||
required
|
<Stack
|
||||||
/>
|
direction={{ base: "column", sm: "row" }}
|
||||||
</div>
|
align={"start"}
|
||||||
</div>
|
justify={"space-between"}
|
||||||
<div className="form-footer">
|
/>
|
||||||
<Button
|
<Button
|
||||||
color="cyan"
|
type="submit"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
className="w-100"
|
bg={"blue.400"}
|
||||||
type="submit">
|
color={"white"}
|
||||||
{intl.formatMessage({
|
_hover={{
|
||||||
id: "login.login",
|
bg: "blue.500",
|
||||||
defaultMessage: "Sign in",
|
}}>
|
||||||
})}
|
{intl.formatMessage({
|
||||||
</Button>
|
id: "login.login",
|
||||||
</div>
|
defaultMessage: "Sign in",
|
||||||
</div>
|
})}
|
||||||
</form>
|
</Button>
|
||||||
</div>
|
</Stack>
|
||||||
|
</form>
|
||||||
|
</Stack>
|
||||||
|
</Box>
|
||||||
|
</Stack>
|
||||||
|
</Flex>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user