Add login page - UI
This commit is contained in:
parent
90824fa963
commit
78427b7bb5
12
cope2n-fe/public/login_bg.svg
Normal file
12
cope2n-fe/public/login_bg.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<svg width="1440" height="1024" viewBox="0 0 1440 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="-839" y="430.089" width="1187" height="1606.01" transform="rotate(-33.9609 -839 430.089)" fill="#134783"/>
|
||||
<rect x="638" y="-77.9104" width="1187" height="1606.01" transform="rotate(-33.9609 638 -77.9104)" fill="#84B1E3"/>
|
||||
<rect x="-303" y="-22.9104" width="1187" height="1606.01" transform="rotate(-33.9609 -303 -22.9104)" fill="#103C74"/>
|
||||
<rect x="-51" y="0.0895996" width="1187" height="1606.01" transform="rotate(-33.9609 -51 0.0895996)" fill="#0C3264"/>
|
||||
<rect x="204" y="0.0895996" width="1187" height="1606.01" transform="rotate(-33.9609 204 0.0895996)" fill="#0B2954"/>
|
||||
<path d="M281 0.0895996L1265.52 -663L2162.68 669.059L1178.16 1332.15L281 0.0895996Z" fill="#0C3265"/>
|
||||
<rect x="430" y="-13.9104" width="1187" height="1606.01" transform="rotate(-33.9609 430 -13.9104)" fill="#092956"/>
|
||||
<path d="M543 -57.9104L1527.52 -721L2424.68 611.059L1440.16 1274.15L543 -57.9104Z" fill="#0C3265"/>
|
||||
<path d="M720 -57.9104L1704.52 -721L2601.68 611.059L1617.16 1274.15L720 -57.9104Z" fill="#0B3F7A"/>
|
||||
<rect x="818" y="-169.91" width="1187" height="1606.01" transform="rotate(-33.9609 818 -169.91)" fill="#0B4C93"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -1,4 +1,4 @@
|
||||
import { Typography } from 'antd';
|
||||
import { Typography } from "antd";
|
||||
|
||||
export const Brand = ({
|
||||
collapsed,
|
||||
@ -13,15 +13,21 @@ export const Brand = ({
|
||||
rows: 1,
|
||||
}}
|
||||
style={{
|
||||
margin: 12,
|
||||
marginTop: 3,
|
||||
marginLeft: 12,
|
||||
marginRight: 12,
|
||||
paddingTop: 18,
|
||||
paddingBottom: 18,
|
||||
letterSpacing: 2,
|
||||
borderRadius: 4,
|
||||
color: 'wheat',
|
||||
textAlign: 'center',
|
||||
border: isBordered ? '4px solid wheat' : 'unset',
|
||||
borderBottomLeftRadius: 10,
|
||||
borderBottomRightRadius: 10,
|
||||
color: "#fff",
|
||||
textAlign: "center",
|
||||
backgroundColor: "rgb(0, 106, 255)",
|
||||
fontSize: 32,
|
||||
}}
|
||||
>
|
||||
{collapsed ? 'O' : 'OCR'}
|
||||
{collapsed ? "O" : "OCR"}
|
||||
</Typography.Title>
|
||||
);
|
||||
};
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Segmented } from 'antd';
|
||||
import styled from 'styled-components';
|
||||
import { useLocalStorage } from 'usehooks-ts';
|
||||
import { LOCALE_KEY } from '../../consts';
|
||||
import { dynamicActivate, getLocale } from '../../i18n';
|
||||
import { Segmented } from "antd";
|
||||
import styled from "styled-components";
|
||||
import { useLocalStorage } from "usehooks-ts";
|
||||
import { LOCALE_KEY } from "../../consts";
|
||||
import { dynamicActivate, getLocale } from "../../i18n";
|
||||
|
||||
const StyledSegmented = styled(Segmented)`
|
||||
& label.ant-segmented-item.ant-segmented-item-selected,
|
||||
@ -23,12 +23,12 @@ export const LanguageSelect = () => {
|
||||
<StyledSegmented
|
||||
options={[
|
||||
{
|
||||
label: 'EN',
|
||||
value: 'en',
|
||||
label: "EN",
|
||||
value: "en",
|
||||
},
|
||||
{
|
||||
label: 'VI',
|
||||
value: 'vi',
|
||||
label: "VI",
|
||||
value: "vi",
|
||||
},
|
||||
]}
|
||||
value={locale}
|
||||
|
@ -105,7 +105,7 @@ export function MainLayout() {
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<LanguageSelect />
|
||||
{/* <LanguageSelect /> */}
|
||||
<Typography.Paragraph
|
||||
style={{
|
||||
marginBottom: 0,
|
||||
|
@ -19,6 +19,7 @@
|
||||
"Confirm": "Confirm",
|
||||
"Copied!": "Copied!",
|
||||
"Copy": "Copy",
|
||||
"Could not login with provided username and password!": "Could not login with provided username and password!",
|
||||
"Create Template": "Create Template",
|
||||
"Create template error: Extracted fields must have unique labels.": "Create template error: Extracted fields must have unique labels.",
|
||||
"Create template error: {0}": "Create template error: {0}",
|
||||
@ -43,12 +44,16 @@
|
||||
"Form number": "Form number",
|
||||
"Home Town": "Home Town",
|
||||
"ID Card": "ID Card",
|
||||
"Intelligent Document Processing Solutions": "Intelligent Document Processing Solutions",
|
||||
"Invoice": "Invoice",
|
||||
"Invoice number": "Invoice number",
|
||||
"Issued By": "Issued By",
|
||||
"Key Information Extraction": "Key Information Extraction",
|
||||
"Label": "Label",
|
||||
"Label of extracted field must not have more than 255 characters": "Label of extracted field must not have more than 255 characters",
|
||||
"Language": "Language",
|
||||
"Login": "Login",
|
||||
"Login with <0>CMC Account</0>?": "Login with <0>CMC Account</0>?",
|
||||
"Name": "Name",
|
||||
"Nation": "Nation",
|
||||
"Nationality": "Nationality",
|
||||
@ -58,8 +63,11 @@
|
||||
"No response.": "No response.",
|
||||
"Number": "Number",
|
||||
"Other Documents": "Other Documents",
|
||||
"Password": "Password",
|
||||
"Please do not leave the label of extracted fields empty": "Please do not leave the label of extracted fields empty",
|
||||
"Please draw at least 1 box of extracted field": "Please draw at least 1 box of extracted field",
|
||||
"Please specify a password": "Please specify a password",
|
||||
"Please specify a username": "Please specify a username",
|
||||
"Please, specify a template name.": "Please, specify a template name.",
|
||||
"Rank": "Rank",
|
||||
"Refresh": "Refresh",
|
||||
@ -92,7 +100,8 @@
|
||||
"Update template successfully.": "Update template successfully.",
|
||||
"Upload file validation": "Upload file validation",
|
||||
"Uploaded image is not match with template.": "Uploaded image is not match with template.",
|
||||
"VAT Invoice": "VAT Invoice",
|
||||
"User log in successfully": "User log in successfully",
|
||||
"Username": "Username",
|
||||
"VAT amount": "VAT amount",
|
||||
"Value": "Value",
|
||||
"You are not allowed to create more than {0} templates! Please contact us for further support.": "You are not allowed to create more than {0} templates! Please contact us for further support.",
|
||||
|
@ -19,6 +19,7 @@
|
||||
"Confirm": "Xác nhận",
|
||||
"Copied!": "Đã sao chép",
|
||||
"Copy": "Sao chép",
|
||||
"Could not login with provided username and password!": "",
|
||||
"Create Template": "Tạo mẫu tài liệu",
|
||||
"Create template error: Extracted fields must have unique labels.": "Tạo mẫu tài liệu lỗi: Các trường thông tin trích xuất phải có nhãn khác nhau.",
|
||||
"Create template error: {0}": "Tạo mẫu tài liệu lỗi: {0}",
|
||||
@ -43,12 +44,16 @@
|
||||
"Form number": "Số mẫu",
|
||||
"Home Town": "Quê quán",
|
||||
"ID Card": "Căn cước công dân",
|
||||
"Intelligent Document Processing Solutions": "",
|
||||
"Invoice": "Hóa đơn",
|
||||
"Invoice number": "Số hóa đơn",
|
||||
"Issued By": "Nơi cấp",
|
||||
"Key Information Extraction": "Trích xuất thông tin",
|
||||
"Label": "Nhãn",
|
||||
"Label of extracted field must not have more than 255 characters": "Độ dài nhãn của trường thông tin trích xuất không được vượt quá 255 kí tự",
|
||||
"Language": "",
|
||||
"Login": "",
|
||||
"Login with <0>CMC Account</0>?": "",
|
||||
"Name": "Tên",
|
||||
"Nation": "Dân tộc",
|
||||
"Nationality": "Quốc tịch",
|
||||
@ -58,8 +63,11 @@
|
||||
"No response.": "Không có kết quả",
|
||||
"Number": "Số",
|
||||
"Other Documents": "Các tài liệu khác",
|
||||
"Password": "",
|
||||
"Please do not leave the label of extracted fields empty": "Vui lòng không bỏ trống nhãn của trường thông tin trích xuất",
|
||||
"Please draw at least 1 box of extracted field": "Vui lòng vẽ ít nhất một hình cho việc trích xuất thông tin",
|
||||
"Please specify a password": "",
|
||||
"Please specify a username": "",
|
||||
"Please, specify a template name.": "Vui lòng nhập tên mẫu",
|
||||
"Rank": "Hạng",
|
||||
"Refresh": "Tải lại",
|
||||
@ -92,7 +100,8 @@
|
||||
"Update template successfully.": "Sửa mẫu tài liệu thành công.",
|
||||
"Upload file validation": "Xác thực tệp tải lên",
|
||||
"Uploaded image is not match with template.": "Ảnh được chọn không khớp với mẫu.",
|
||||
"VAT Invoice": "Hóa đơn VAT",
|
||||
"User log in successfully": "",
|
||||
"Username": "",
|
||||
"VAT amount": "Tổng tiền VAT",
|
||||
"Value": "Giá trị",
|
||||
"You are not allowed to create more than {0} templates! Please contact us for further support.": "Hệ thống không cho phép tạo nhiều hơn {0} mẫu tài liệu. Vui lòng liên hệ chúng tôi để được hỗ trợ thêm.",
|
||||
|
@ -6,6 +6,7 @@ import DriverLicensePage from '../views/driver-license';
|
||||
import IDCardPage from '../views/id-card';
|
||||
import InvoicePage from '../views/invoice';
|
||||
import OtherDocumentsPage from '../views/other-documents';
|
||||
import LoginPage from '../views/login';
|
||||
import { PrivateRoute } from './guard-route';
|
||||
|
||||
const ConfigTemplatePage = React.lazy(() => import('../views/config-template'));
|
||||
@ -28,6 +29,10 @@ export function createRouter() {
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
element: <LoginPage />,
|
||||
},
|
||||
{
|
||||
path: '/error/401',
|
||||
element: (
|
||||
|
@ -31,5 +31,5 @@ export function PrivateRoute({
|
||||
return <>{element}</>;
|
||||
}
|
||||
|
||||
return <Navigate to="/error/401" />;
|
||||
return <Navigate to="/login" />;
|
||||
}
|
||||
|
197
cope2n-fe/src/views/login/index.tsx
Normal file
197
cope2n-fe/src/views/login/index.tsx
Normal file
@ -0,0 +1,197 @@
|
||||
import { LockOutlined, UserOutlined } from "@ant-design/icons";
|
||||
import { t, Trans } from "@lingui/macro";
|
||||
import { useLingui } from "@lingui/react";
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Form,
|
||||
FormProps,
|
||||
Input,
|
||||
notification,
|
||||
Row,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import { AxiosError } from "axios";
|
||||
import { useEffect, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import { LanguageSelect } from "../../components/language-select";
|
||||
import { TOKEN_KEY } from "../../consts";
|
||||
import "./style.css";
|
||||
|
||||
export const DIVERGE_FORM_ICON_COLOR = "rgba(0, 0, 0, 0.25)";
|
||||
|
||||
export function getErrorMessage(error: any) {
|
||||
if (error instanceof AxiosError) {
|
||||
return error.response?.data?.message;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
export interface LogInPayload {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
const StyledLoginContainer = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url(/login_bg.svg);
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const StyledFormContainer = styled(Form)`
|
||||
.login-form__title {
|
||||
color: white;
|
||||
text-align: justify;
|
||||
font-size: 52px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.login-form__subtitle {
|
||||
color: #84b1e3;
|
||||
text-align: right;
|
||||
font-weight: 300;
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.login-form__form-title {
|
||||
color: white;
|
||||
text-align: right;
|
||||
font-weight: 400;
|
||||
margin-top: 0.8em !important;
|
||||
}
|
||||
|
||||
.ant-form-item-control-input .ant-input-affix-wrapper {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.login-form__helper-text {
|
||||
font-size: 18px;
|
||||
color: #83afdf;
|
||||
}
|
||||
|
||||
.login-form__submit-button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
a {
|
||||
color: white;
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
|
||||
export default function LoginPage() {
|
||||
const { i18n } = useLingui();
|
||||
const [isAuthenticating, setIsAuthenticating] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const token = localStorage.getItem(TOKEN_KEY);
|
||||
if (token) {
|
||||
window.location.href = "/";
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleLogin: FormProps<LogInPayload>["onFinish"] = async (payload) => {
|
||||
setIsAuthenticating(true);
|
||||
try {
|
||||
const resp = await fetch("/api/ctel/login/", {
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
method: "POST",
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
const message = await resp.json();
|
||||
if (!resp.ok) {
|
||||
throw new Error(message?.detail);
|
||||
}
|
||||
let token = message?.token || "";
|
||||
token = token.trim();
|
||||
localStorage.setItem(TOKEN_KEY, JSON.stringify(token));
|
||||
notification.success({
|
||||
message: t(i18n)`User log in successfully`,
|
||||
});
|
||||
setTimeout(() => {
|
||||
window.location.href = "/";
|
||||
}, 1000);
|
||||
} catch (error) {
|
||||
setIsAuthenticating(false);
|
||||
notification.error({
|
||||
message: t(i18n)`Could not login with provided username and password!`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledLoginContainer>
|
||||
<StyledFormContainer className="login-form" onFinish={handleLogin as any}>
|
||||
<Typography.Title level={1} className="login-form__title">
|
||||
OCR APIs
|
||||
</Typography.Title>
|
||||
<Typography.Title level={3} className="login-form__subtitle">
|
||||
<Trans>Intelligent Document Processing Solutions</Trans>
|
||||
</Typography.Title>
|
||||
<Typography.Title level={2} className="login-form__form-title">
|
||||
<Trans>Login</Trans>
|
||||
</Typography.Title>
|
||||
|
||||
<Form.Item
|
||||
name="username"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: t(i18n)`Please specify a username`,
|
||||
whitespace: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
autoComplete="username"
|
||||
prefix={<UserOutlined style={{ color: DIVERGE_FORM_ICON_COLOR }} />}
|
||||
placeholder={t(i18n)`Username`}
|
||||
size="large"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="password"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: t(i18n)`Please specify a password`,
|
||||
whitespace: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input.Password
|
||||
autoComplete="current-password"
|
||||
prefix={<LockOutlined style={{ color: DIVERGE_FORM_ICON_COLOR }} />}
|
||||
placeholder={t(i18n)`Password`}
|
||||
size="large"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
className="login-form__submit-button"
|
||||
size="large"
|
||||
loading={isAuthenticating}
|
||||
>
|
||||
{t(i18n)`Login`}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</StyledFormContainer>
|
||||
</StyledLoginContainer>
|
||||
);
|
||||
}
|
4
cope2n-fe/src/views/login/style.css
Normal file
4
cope2n-fe/src/views/login/style.css
Normal file
@ -0,0 +1,4 @@
|
||||
#root {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
}
|
Loading…
Reference in New Issue
Block a user