feat: login & home
This commit is contained in:
parent
c1b7cdcbfa
commit
06358334c4
14
src/App.tsx
14
src/App.tsx
@ -1,13 +1,19 @@
|
||||
import React from 'react';
|
||||
import { Routes, Route } from "react-router-dom";
|
||||
import { BreakpointProvider } from './components/Breakpoint'
|
||||
import { Header } from './components/Layout/Header'
|
||||
import { Footer } from './components/Layout/Footer'
|
||||
import { Layout } from './components/Layout'
|
||||
import { Home } from './pages/activation/home'
|
||||
import { Login } from './pages/activation/login'
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<BreakpointProvider>
|
||||
<Header showActiveKit showMenu />
|
||||
<Footer />
|
||||
<Routes>
|
||||
<Route path="/" element={<Layout />}>
|
||||
<Route index element={<Home />} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</BreakpointProvider>
|
||||
);
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ export const useBreakpoint = (props: BreakpointProps): boolean => {
|
||||
return size.width === breakpointList[key as keyof typeof breakpointList]
|
||||
}
|
||||
if (key === 'width') {
|
||||
return matchWidth(3000, props.width?.min, props.width?.max)
|
||||
return matchWidth(size.width, props.width?.min, props.width?.max)
|
||||
}
|
||||
if (key === 'landscape' || key === 'portrait') {
|
||||
return key === orientation
|
||||
|
20
src/components/FormControl/ErrorMessage.tsx
Normal file
20
src/components/FormControl/ErrorMessage.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import React, { FC, PropsWithChildren } from 'react'
|
||||
import { InfoCircleOutlined } from '@ant-design/icons'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const StyledIcon = styled(InfoCircleOutlined)`
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
font-size: 14px;
|
||||
line-height: 16px;
|
||||
vertical-align: top;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
font-size: 8px;
|
||||
margin-right: 4px;
|
||||
line-height: 10px;
|
||||
}
|
||||
`
|
||||
export const ErrorMessage: FC<PropsWithChildren<{ message?: string }>> = ({ message, children }) => {
|
||||
return <><StyledIcon />{children || message}</>
|
||||
}
|
31
src/components/FormControl/LabelWithTooltip.tsx
Normal file
31
src/components/FormControl/LabelWithTooltip.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React, { FC, PropsWithChildren } from 'react'
|
||||
import { InfoCircleOutlined } from '@ant-design/icons'
|
||||
import { Tooltip } from '../Tooltip'
|
||||
import styled from 'styled-components'
|
||||
|
||||
const StyledLabel = styled.span`
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const StyledIcon = styled(InfoCircleOutlined)`
|
||||
position: absolute;
|
||||
font-size: 16px;
|
||||
color: #000022;
|
||||
right: 0;
|
||||
`
|
||||
export const LabelWithTooltip: FC<PropsWithChildren<{ title: string; primary?: boolean }>> = ({
|
||||
primary,
|
||||
children,
|
||||
title
|
||||
}) => {
|
||||
return (
|
||||
<StyledLabel>
|
||||
{children}
|
||||
<Tooltip closeable title={title} primary={primary} placement="left">
|
||||
<StyledIcon />
|
||||
</Tooltip>
|
||||
</StyledLabel>
|
||||
)
|
||||
}
|
@ -5,3 +5,5 @@ export * from './Select'
|
||||
export * from './Chekbox'
|
||||
export * from './Radio'
|
||||
export * from './RadioGroup'
|
||||
export * from './ErrorMessage'
|
||||
export * from './LabelWithTooltip'
|
||||
|
@ -119,12 +119,12 @@ const TinyFooter = styled.footer`
|
||||
const StyledFooter: FC<PropsWithChildren> = ({ children }) => {
|
||||
return (
|
||||
<>
|
||||
<Breakpoint s down>
|
||||
<Breakpoint width={{ max: 520 }}>
|
||||
<TinyFooter>
|
||||
{children}
|
||||
</TinyFooter>
|
||||
</Breakpoint>
|
||||
<Breakpoint s up>
|
||||
<Breakpoint width={{ min: 520 }}>
|
||||
<LargeFooter>
|
||||
{children}
|
||||
</LargeFooter>
|
||||
@ -133,9 +133,9 @@ const StyledFooter: FC<PropsWithChildren> = ({ children }) => {
|
||||
)
|
||||
|
||||
}
|
||||
export const Footer: FC = () => {
|
||||
export const Footer: FC<{ className?: string }> = (props) => {
|
||||
return (
|
||||
<StyledFooter>
|
||||
<StyledFooter {...props}>
|
||||
<Links>
|
||||
{
|
||||
FooterLinks.map((link, index) => (
|
||||
|
@ -20,7 +20,6 @@ const StyledHeader = styled.header`
|
||||
border-bottom: 2px solid #000022;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledLogo = styled(Logo)`
|
||||
width: 171px;
|
||||
height: 41px;
|
||||
@ -78,11 +77,12 @@ const StyledButton = styled(Button)`
|
||||
height: 58px;
|
||||
|
||||
${props => props.theme.breakpoints.down(600)} {
|
||||
width: 160px;
|
||||
width: 130px;
|
||||
}
|
||||
`
|
||||
|
||||
export interface HeaderProps {
|
||||
className?: string
|
||||
showMenu?: boolean
|
||||
onClickMenu?: () => void
|
||||
showActiveKit?: boolean;
|
||||
@ -101,10 +101,11 @@ export const Header: FC<HeaderProps> = ({
|
||||
showMenu,
|
||||
onClickMenu,
|
||||
showActiveKit,
|
||||
activeKitHref
|
||||
activeKitHref,
|
||||
className
|
||||
}) => {
|
||||
return (
|
||||
<StyledHeader>
|
||||
<StyledHeader className={className}>
|
||||
{showMenu &&
|
||||
<Breakpoint s down>
|
||||
<StyledMenuIcon component={Menu} onClick={onClickMenu} />
|
||||
@ -115,7 +116,8 @@ export const Header: FC<HeaderProps> = ({
|
||||
<Breakpoint s up>
|
||||
<StyledButton href={activeKitHref}>Activate Kit</StyledButton>
|
||||
</Breakpoint>}
|
||||
<StyledCartWrapper target="_blank" rel="noopener noreferrer" href="https://ihealthlabs.com/products/checkmesafe-home-collection-kit-C2">
|
||||
<StyledCartWrapper target="_blank" rel="noopener noreferrer"
|
||||
href="https://ihealthlabs.com/products/checkmesafe-home-collection-kit-C2">
|
||||
<Icon component={Cart} />
|
||||
<StyledText>SHOP</StyledText>
|
||||
</StyledCartWrapper>
|
||||
|
36
src/components/Layout/Layout.tsx
Normal file
36
src/components/Layout/Layout.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import React, { FC, PropsWithChildren } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { Header, HeaderProps } from './Header'
|
||||
import { Footer } from './Footer'
|
||||
import { Outlet } from 'react-router-dom'
|
||||
|
||||
const StyledSection = styled.section`
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
const StyledHeader = styled(Header)`
|
||||
flex: 0 0 auto;
|
||||
`
|
||||
|
||||
const StyledFooter = styled(Footer)`
|
||||
flex: 0 0 auto;
|
||||
`
|
||||
|
||||
const StyledMain = styled.main`
|
||||
flex: auto;
|
||||
`
|
||||
|
||||
export const Layout: FC<PropsWithChildren<Omit<HeaderProps, 'className'>>> = ({ children, ...headerProps }) => {
|
||||
return (
|
||||
<StyledSection>
|
||||
<StyledHeader {...headerProps} />
|
||||
<StyledMain>
|
||||
<Outlet />
|
||||
{children}
|
||||
</StyledMain>
|
||||
<StyledFooter />
|
||||
</StyledSection>
|
||||
)
|
||||
}
|
1
src/components/Layout/index.ts
Normal file
1
src/components/Layout/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Layout'
|
@ -1,15 +1,19 @@
|
||||
import React, { FC, useCallback } from 'react'
|
||||
import React, { FC, ReactNode, useCallback } from 'react'
|
||||
import { Tooltip as AntdTooltip, TooltipProps as AntdTooltipProps } from 'antd'
|
||||
import { ReactComponent as CloseIcon } from '../../icons/close.svg'
|
||||
import styled from 'styled-components'
|
||||
|
||||
export type TooltipProps = AntdTooltipProps & { closeable?: boolean; primary?: boolean }
|
||||
export type TooltipProps = Omit<AntdTooltipProps, 'title'> & {
|
||||
closeable?: boolean; primary?: boolean;
|
||||
title: ReactNode
|
||||
}
|
||||
|
||||
const Close = styled.span`
|
||||
line-height: 17px;
|
||||
line-height: 25px;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
padding-left: 4px;
|
||||
display: inline-block;
|
||||
|
||||
> svg {
|
||||
width: 14px;
|
||||
@ -27,7 +31,8 @@ export const Tooltip: FC<TooltipProps> = ({ closeable, primary, title, overlayCl
|
||||
trigger="click"
|
||||
overlayClassName={`health-tooltip${primary ? '-highlighted' : ''} ${overlayClassName || ''}`}
|
||||
{...props}
|
||||
title={<>{title} {closeable ? <Close><CloseIcon onClick={handleClick} /></Close> : null}</>}
|
||||
title={<><span style={{ display: 'inline-block' }}>{title}</span> {closeable ?
|
||||
<Close><CloseIcon onClick={handleClick} /></Close> : null}</>}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client';
|
||||
import { ConfigProvider } from 'antd'
|
||||
import { Theme } from './theme'
|
||||
import { ThemeProvider } from './theme/ThemeProvider'
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import App from './App';
|
||||
import 'typeface-lato'
|
||||
import 'antd/dist/reset.css';
|
||||
@ -13,10 +14,12 @@ const root = ReactDOM.createRoot(
|
||||
);
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<BrowserRouter>
|
||||
<ConfigProvider theme={Theme}>
|
||||
<ThemeProvider>
|
||||
<App />
|
||||
</ThemeProvider>
|
||||
</ConfigProvider>
|
||||
</BrowserRouter>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
20
src/pages/activation/home/Home.tsx
Normal file
20
src/pages/activation/home/Home.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import React, { FC } from 'react'
|
||||
import { StyledContainer, StyledImageWrapper, StyledMainContent, StyledTitle, StyledContent } from './styled'
|
||||
import { Divider } from 'antd'
|
||||
import { Button } from '../../../components/Button'
|
||||
|
||||
export const Home: FC = () => {
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledImageWrapper />
|
||||
<StyledMainContent>
|
||||
<StyledTitle level={3}>Welcome to iHealth CheckMeSafe!</StyledTitle>
|
||||
<StyledContent>
|
||||
<Button block href="/activate">Activate Kit</Button>
|
||||
<Divider plain>Or</Divider>
|
||||
<Button block type="default" href="/login">Log in</Button>
|
||||
</StyledContent>
|
||||
</StyledMainContent>
|
||||
</StyledContainer>
|
||||
)
|
||||
}
|
BIN
src/pages/activation/home/images/homePic@2x.png
Normal file
BIN
src/pages/activation/home/images/homePic@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 MiB |
1
src/pages/activation/home/index.ts
Normal file
1
src/pages/activation/home/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Home'
|
57
src/pages/activation/home/styled.tsx
Normal file
57
src/pages/activation/home/styled.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import Image from './images/homePic@2x.png'
|
||||
import { Title } from '../../../components/Typography'
|
||||
|
||||
export const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 25px 0 25px 40px;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
padding: 4px 23px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
`
|
||||
export const StyledImageWrapper = styled.div`
|
||||
width: 54%;
|
||||
max-width: 737px;
|
||||
height: 773px;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-image: url(${Image});
|
||||
flex: 0 0 auto;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
width: 100%;
|
||||
height: 393px;
|
||||
max-width: 344px;
|
||||
}
|
||||
`
|
||||
|
||||
export const StyledMainContent = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
flex: auto;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
width: 100%;
|
||||
padding-top: 26px;
|
||||
}
|
||||
`
|
||||
|
||||
export const StyledTitle = styled(Title)`
|
||||
`
|
||||
export const StyledContent = styled.div`
|
||||
margin-top: 44px;
|
||||
width: 358px;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
width: 100%;
|
||||
margin-top: 26px;
|
||||
}
|
||||
`
|
209
src/pages/activation/login/ForgetPasswordModal.tsx
Normal file
209
src/pages/activation/login/ForgetPasswordModal.tsx
Normal file
@ -0,0 +1,209 @@
|
||||
import React, { FC, useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { Modal, ModalProps } from '../../../components/Modal'
|
||||
import { Title, Paragraph, Link } from '../../../components/Typography'
|
||||
import { Form } from 'antd'
|
||||
import { Button } from '../../../components/Button'
|
||||
import { Input, Password, ErrorMessage, LabelWithTooltip } from '../../../components/FormControl'
|
||||
import Image from './images/password@2x.png'
|
||||
|
||||
// TODO: Captcha
|
||||
type Props = Pick<ModalProps, 'open'> & {
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
min-height: 366px;
|
||||
|
||||
${props => props.theme.breakpoints.up('s')} {
|
||||
justify-content: center;
|
||||
}
|
||||
`
|
||||
const StyledMain = styled.div`
|
||||
width: 100%;
|
||||
|
||||
${props => props.theme.breakpoints.up('s')} {
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
padding-top: 37px;
|
||||
}
|
||||
|
||||
.ant-form-item-required {
|
||||
width: 100%;
|
||||
}
|
||||
`
|
||||
const StyledTitle = styled(Title)`
|
||||
margin-bottom: 2px !important;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
margin-bottom: 5px !important;
|
||||
}
|
||||
`
|
||||
const StyledCaptchaHint = styled(Paragraph)`
|
||||
margin-top: 30px;
|
||||
`
|
||||
const TitleCopies = [
|
||||
'Forgot Password',
|
||||
'Reset Password',
|
||||
'Reset Password',
|
||||
'Password Reset'
|
||||
]
|
||||
|
||||
const DescriptionCopies = [
|
||||
'Enter your email address to reset your password.',
|
||||
'A 6-digit code has been sent to John.smith@gmail.com',
|
||||
]
|
||||
|
||||
export const ForgetPasswordModal: FC<Props> = ({ onClose, ...props }) => {
|
||||
const [step, setStep] = useState(0)
|
||||
const [email, setEmail] = useState('')
|
||||
const [captcha, setCaptcha] = useState('')
|
||||
const [expiration, setExpiration] = useState('9:45')
|
||||
const handleFinishEmail = ({ email }: { email: string }) => {
|
||||
setEmail(email)
|
||||
setStep(1)
|
||||
}
|
||||
|
||||
const handleFinishCaptcha = ({ captcha }: { captcha: string }) => {
|
||||
setCaptcha(captcha)
|
||||
setStep(2)
|
||||
}
|
||||
|
||||
const handleSendAgain = () => {
|
||||
// TODO: Update expiration
|
||||
|
||||
}
|
||||
|
||||
const handleConfirm = (data: { password: string }) => {
|
||||
console.log(data)
|
||||
setStep(3)
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
setStep(0)
|
||||
setEmail('')
|
||||
setCaptcha('')
|
||||
|
||||
onClose && onClose()
|
||||
}
|
||||
|
||||
const handleLogin = () => {
|
||||
// TODO: login
|
||||
}
|
||||
return (
|
||||
<Modal {...props} width={688} destroyOnClose onCancel={handleClose}>
|
||||
<StyledWrapper>
|
||||
<StyledMain>
|
||||
<StyledTitle level={2}>{TitleCopies[step]}</StyledTitle>
|
||||
{step < 2 && <Paragraph>{DescriptionCopies[step]}</Paragraph>}
|
||||
{step === 0 ? (
|
||||
<Form
|
||||
layout="vertical"
|
||||
style={{ marginTop: 4 }}
|
||||
onFinish={handleFinishEmail}>
|
||||
<Form.Item
|
||||
name="email"
|
||||
rules={[
|
||||
{
|
||||
required: true, message: <ErrorMessage message="Please input your email" />
|
||||
},
|
||||
{ type: 'email', message: <ErrorMessage message="Invalid email" /> }
|
||||
]}
|
||||
>
|
||||
<Input placeholder="email@example.com" />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button style={{ marginTop: 20 }} block htmlType="submit">Next</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
) : null}
|
||||
{step === 1 ? (
|
||||
<Form
|
||||
layout="vertical"
|
||||
style={{ marginTop: 27 }}
|
||||
onFinish={handleFinishCaptcha}>
|
||||
<Form.Item
|
||||
name="captcha"
|
||||
rules={[
|
||||
{
|
||||
required: true, message: <ErrorMessage message="Please input your code" />
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<StyledCaptchaHint>
|
||||
The code will expire in {expiration}<br />
|
||||
Didn’t receive the code? <Link onClick={handleSendAgain}>Send again</Link>
|
||||
</StyledCaptchaHint>
|
||||
<Form.Item>
|
||||
<Button block htmlType="submit">Verify</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
) : null}
|
||||
{step === 2 ? (
|
||||
<Form
|
||||
layout="vertical"
|
||||
onFinish={handleConfirm}
|
||||
>
|
||||
<Form.Item
|
||||
label={
|
||||
<LabelWithTooltip
|
||||
primary
|
||||
title='The minimum password length is 8 characters and must contain at least 1 uppercase letter, 1 lowercase letter, 1 number, and 1 special character (!@#$%^&*).'
|
||||
>
|
||||
Please enter a new password
|
||||
</LabelWithTooltip>
|
||||
}
|
||||
name="password"
|
||||
rules={[
|
||||
{ required: true, message: <ErrorMessage message="Please input your password" /> },
|
||||
{
|
||||
pattern: /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/,
|
||||
message: <ErrorMessage message="Invalid password" />
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Password placeholder="********" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Confirm password"
|
||||
name="repeatPassword"
|
||||
dependencies={['password']}
|
||||
rules={[{ required: true, message: <ErrorMessage message="Please confirm your password" /> },
|
||||
({ getFieldValue }) => ({
|
||||
validator(_, value) {
|
||||
if (!value || getFieldValue('password') === value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error('The two passwords that you entered do not match'));
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Password placeholder="********" />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Button style={{ marginTop: 17 }} block htmlType="submit">Confirm</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
) : null}
|
||||
{step === 3 ? (
|
||||
<>
|
||||
<Paragraph style={{ marginTop: 20, marginBottom: 16 }}>
|
||||
Your password has just been reset, please log in to continue.
|
||||
</Paragraph>
|
||||
<img src={Image} style={{ width: 232, height: 83, margin: 'auto', display: 'block' }} />
|
||||
<Button block style={{ marginTop: 20 }} onClick={handleLogin}>Log in</Button>
|
||||
</>
|
||||
) : null}
|
||||
</StyledMain>
|
||||
</StyledWrapper>
|
||||
</Modal>
|
||||
)
|
||||
}
|
75
src/pages/activation/login/Login.tsx
Normal file
75
src/pages/activation/login/Login.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import React, { FC, useState } from 'react'
|
||||
import {
|
||||
StyledContainer,
|
||||
StyledImageWrapper,
|
||||
StyledMainContent,
|
||||
StyledHeadline,
|
||||
StyledContent,
|
||||
StyledButton,
|
||||
StyledTitle, StyledHint, StyledHelp
|
||||
} from './styled'
|
||||
import { Form } from 'antd'
|
||||
import { Paragraph, Link } from '../../../components/Typography'
|
||||
import { Input, Password, ErrorMessage } from '../../../components/FormControl'
|
||||
import { ForgetPasswordModal } from './ForgetPasswordModal'
|
||||
|
||||
export const Login: FC = () => {
|
||||
const [showModal, setModal] = useState(false)
|
||||
const handleShowModal = () => {
|
||||
setModal(true)
|
||||
}
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setModal(false)
|
||||
}
|
||||
const onFinish = (values: any) => {
|
||||
// TODO: login
|
||||
console.log('Success:', values);
|
||||
};
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledImageWrapper />
|
||||
<StyledMainContent>
|
||||
<StyledHeadline level={1} style={{ fontSize: 36 }}>Welcome Back!</StyledHeadline>
|
||||
<StyledTitle level={3}>Log in to continue</StyledTitle>
|
||||
<StyledContent>
|
||||
<Form
|
||||
layout="vertical"
|
||||
onFinish={onFinish}
|
||||
>
|
||||
<Form.Item
|
||||
label="Email"
|
||||
name="email"
|
||||
rules={[
|
||||
{
|
||||
required: true, message: <ErrorMessage message="Please input your email" />
|
||||
},
|
||||
{ type: 'email', message: <ErrorMessage message="Invalid email" /> }
|
||||
]}
|
||||
>
|
||||
<Input placeholder="email@example.com" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Password"
|
||||
name="password"
|
||||
rules={[{ required: true, message: <ErrorMessage message="Please input your password" /> }]}
|
||||
extra={<StyledHelp type="text" onClick={handleShowModal}>Forgot password</StyledHelp>}
|
||||
>
|
||||
<Password placeholder="********" />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<StyledButton block htmlType="submit">Log in</StyledButton>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<StyledHint>
|
||||
<Paragraph>
|
||||
Don’t have an account?
|
||||
<Link href="/activate">Activate your kit</Link>
|
||||
</Paragraph>
|
||||
</StyledHint>
|
||||
</StyledContent>
|
||||
</StyledMainContent>
|
||||
<ForgetPasswordModal open={showModal} onClose={handleCloseModal} />
|
||||
</StyledContainer>
|
||||
)
|
||||
}
|
BIN
src/pages/activation/login/images/loginPic@2x.png
Normal file
BIN
src/pages/activation/login/images/loginPic@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 MiB |
BIN
src/pages/activation/login/images/password@2x.png
Normal file
BIN
src/pages/activation/login/images/password@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
1
src/pages/activation/login/index.ts
Normal file
1
src/pages/activation/login/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Login'
|
80
src/pages/activation/login/styled.tsx
Normal file
80
src/pages/activation/login/styled.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import Image from './images/loginPic@2x.png'
|
||||
import { Title } from '../../../components/Typography'
|
||||
import { Button } from '../../../components/Button'
|
||||
|
||||
export const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 25px 0 25px 40px;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
padding: 16px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
`
|
||||
export const StyledImageWrapper = styled.div`
|
||||
width: 54%;
|
||||
max-width: 737px;
|
||||
height: 803px;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-image: url(${Image});
|
||||
flex: 0 0 auto;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
|
||||
export const StyledMainContent = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
flex: auto;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
width: 100%;
|
||||
padding-top: 50px;
|
||||
}
|
||||
`
|
||||
|
||||
export const StyledHeadline = styled(Title)`
|
||||
margin-bottom: 10px !important;
|
||||
font-size: 36px;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
`
|
||||
|
||||
export const StyledTitle = styled(Title)`
|
||||
margin-top: 0 !important;
|
||||
`
|
||||
export const StyledContent = styled.div`
|
||||
margin-top: 52px;
|
||||
width: 358px;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
width: 100%;
|
||||
margin-top: 47px;
|
||||
}
|
||||
`
|
||||
|
||||
export const StyledButton = styled(Button)`
|
||||
margin-top: 30px;
|
||||
`
|
||||
|
||||
export const StyledHelp = styled(Button)`
|
||||
padding: 0;
|
||||
font-size: 16px;
|
||||
`
|
||||
|
||||
export const StyledHint = styled.div`
|
||||
text-align: center;
|
||||
margin-top: 33px;
|
||||
`
|
@ -18,6 +18,7 @@ html, body {
|
||||
.ant-btn-link, .ant-btn-text {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.ant-wave {
|
||||
@ -48,6 +49,7 @@ html, body {
|
||||
}
|
||||
|
||||
.health-tooltip-highlighted.ant-tooltip .ant-tooltip-inner {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
box-shadow: 1px 1px 0px 0 #FF5A0C, -1px -1px 0px 0 #FF5A0C, -1px 1px 0px 0 #FF5A0C, 1px -1px 0px 0 #FF5A0C;
|
||||
}
|
||||
@ -58,6 +60,7 @@ html, body {
|
||||
|
||||
.health-tooltip.ant-tooltip .ant-tooltip-inner {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
box-shadow: 0 2px 0px 0 #000022, 0 2px 0px 0px #000022, 1px 1px 0px 2px #000022;
|
||||
}
|
||||
|
||||
@ -191,3 +194,27 @@ html, body {
|
||||
.ant-form-item {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
|
||||
@media (min-width: 500px) {
|
||||
.ant-form-item {
|
||||
margin-bottom: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 500px) {
|
||||
.ant-form-item .ant-form-item-explain-error {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
|
||||
.ant-modal .ant-modal-content {
|
||||
padding-left: 18px;
|
||||
padding-right: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-form-item .ant-form-item-label > label .ant-form-item-tooltip {
|
||||
color: #000022;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { ThemeConfig } from 'antd'
|
||||
|
||||
export const breakpointList = {
|
||||
xs: 0,
|
||||
s: 500,
|
||||
@ -53,6 +54,11 @@ export const Theme: ThemeConfig = {
|
||||
colorBgTextHover: 'transparent',
|
||||
colorBgTextActive: 'transparent',
|
||||
},
|
||||
Divider: {
|
||||
colorSplit: '#000',
|
||||
colorText: '#000',
|
||||
fontSize: 20,
|
||||
},
|
||||
Modal: {
|
||||
borderRadiusLG: 10,
|
||||
boxShadow: '2px 2px 4px 1px rgba(0, 0, 0, 0.2)',
|
||||
|
Loading…
Reference in New Issue
Block a user