Signup page conversion to chakra

This commit is contained in:
Jamie Curnow
2021-11-08 23:42:23 +10:00
parent dea89bd312
commit fc831e7298
7 changed files with 164 additions and 211 deletions

View File

@@ -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",

View File

@@ -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&amp;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"

View File

@@ -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>
); );

View File

@@ -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>
); );
}; };

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -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;
}
}
}
*/

View File

@@ -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>
); );
} }