feat: active
@ -16,6 +16,7 @@
|
||||
"react-scripts": "5.0.1",
|
||||
"styled-components": "^5.3.9",
|
||||
"typeface-lato": "^1.1.13",
|
||||
"typeface-roboto": "^1.1.13",
|
||||
"typescript": "^4.9.5",
|
||||
"ua-parser-js": "^1.0.35"
|
||||
},
|
||||
|
135
src/components/FormControl/Captcha.tsx
Normal file
@ -0,0 +1,135 @@
|
||||
import React, { ChangeEvent, FC, FocusEvent, KeyboardEvent, MouseEvent, RefObject } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
export interface CaptchaProps {
|
||||
length?: number
|
||||
value?: string
|
||||
onChange?: (value: string) => void
|
||||
}
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 14px;
|
||||
`
|
||||
const StyledInputWrapper = styled.div`
|
||||
flex: 0 0 40px;
|
||||
height: 48px;
|
||||
`
|
||||
const StyledInput = styled.input`
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
padding: 8px;
|
||||
font-size: 28px;
|
||||
font-weight: 500;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
border-radius: 2px;
|
||||
box-sizing: border-box;
|
||||
border: 2px solid #000022;
|
||||
|
||||
&:focus {
|
||||
text-decoration: underline;
|
||||
outline: 2px solid ${({ theme }) => theme.brandPrimary};
|
||||
}
|
||||
`
|
||||
|
||||
function textSelect(element: HTMLInputElement): void {
|
||||
const start = 0;
|
||||
const end = element.value.length;
|
||||
element.setSelectionRange(start, end);
|
||||
element.focus();
|
||||
}
|
||||
|
||||
export const Captcha: FC<CaptchaProps> = ({ value, onChange, length = 6 }) => {
|
||||
const inputDomRefs: RefObject<HTMLInputElement>[] = Array.from({ length }, () => React.createRef<HTMLInputElement>())
|
||||
const getCode = (index: number): string => {
|
||||
return value && value.length > index ? value[index] : ''
|
||||
}
|
||||
const getNewValue = (newValue: string, index: number) => {
|
||||
const oldValue = Array.from({ length }, (_, i) => getCode(i))
|
||||
oldValue[index] = newValue
|
||||
return oldValue.map(v => v || ' ').join('').trimEnd()
|
||||
}
|
||||
const focusOn = (index: number, select?: boolean) => {
|
||||
if (inputDomRefs[index] && inputDomRefs[index].current) {
|
||||
inputDomRefs[index].current?.focus()
|
||||
if (select) {
|
||||
textSelect(inputDomRefs[index].current!)
|
||||
}
|
||||
}
|
||||
}
|
||||
const handleFocus = (e: FocusEvent<HTMLInputElement>) => {
|
||||
textSelect(e.target)
|
||||
}
|
||||
const handleClick = (e: MouseEvent<HTMLInputElement>) => {
|
||||
textSelect(e.target as HTMLInputElement)
|
||||
}
|
||||
const handleChange = (index: number) => (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const value = e.target.value.trim()
|
||||
if (onChange) {
|
||||
onChange(getNewValue(value, index))
|
||||
}
|
||||
textSelect(e.target)
|
||||
if (value) {
|
||||
focusOn(index + 1)
|
||||
}
|
||||
}
|
||||
const handleKeydown = (index: number) => (e: KeyboardEvent<HTMLInputElement>) => {
|
||||
switch (e.key) {
|
||||
case 'Backspace':
|
||||
// @ts-ignore
|
||||
if (e.target.value === "") {
|
||||
e.stopPropagation();
|
||||
focusOn(index - 1)
|
||||
}
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
case 'ArrowUp':
|
||||
e.stopPropagation();
|
||||
if (inputDomRefs[index - 1]) {
|
||||
focusOn(index - 1)
|
||||
} else {
|
||||
focusOn(index)
|
||||
}
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
case 'ArrowDown':
|
||||
e.stopPropagation();
|
||||
if (inputDomRefs[index + 1]) {
|
||||
focusOn(index + 1)
|
||||
} else {
|
||||
focusOn(index)
|
||||
}
|
||||
break;
|
||||
default:
|
||||
textSelect(e.target as HTMLInputElement)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return (
|
||||
<StyledWrapper>
|
||||
{
|
||||
Array.from({ length }, () => "").map((_, index) => {
|
||||
return (
|
||||
<StyledInputWrapper key={index}>
|
||||
<StyledInput
|
||||
type="text"
|
||||
value={getCode(index)}
|
||||
ref={inputDomRefs[index]}
|
||||
onFocus={handleFocus}
|
||||
onClick={handleClick}
|
||||
onChange={handleChange(index)}
|
||||
onKeyDown={handleKeydown(index)}
|
||||
spellCheck="false"
|
||||
autoComplete="false"
|
||||
autoCorrect="false"
|
||||
autoCapitalize="off"
|
||||
/>
|
||||
</StyledInputWrapper>
|
||||
)
|
||||
})
|
||||
}
|
||||
</StyledWrapper>
|
||||
)
|
||||
}
|
@ -10,9 +10,9 @@ const StyledIcon = styled(InfoCircleOutlined)`
|
||||
vertical-align: top;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
font-size: 8px;
|
||||
font-size: 12px;
|
||||
margin-right: 4px;
|
||||
line-height: 10px;
|
||||
line-height: 18px;
|
||||
}
|
||||
`
|
||||
export const ErrorMessage: FC<PropsWithChildren<{ message?: string }>> = ({ message, children }) => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { FC, PropsWithChildren } from 'react'
|
||||
import React, { FC, MouseEvent, PropsWithChildren } from 'react'
|
||||
import { InfoCircleOutlined } from '@ant-design/icons'
|
||||
import { Tooltip } from '../Tooltip'
|
||||
import styled from 'styled-components'
|
||||
@ -9,12 +9,20 @@ const StyledLabel = styled.span`
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const StyledIcon = styled(InfoCircleOutlined)`
|
||||
position: absolute;
|
||||
font-size: 16px;
|
||||
color: #000022;
|
||||
const StyledSpan = styled.span`
|
||||
right: 0;
|
||||
color: #000022;
|
||||
font-size: 16px;
|
||||
position: absolute;
|
||||
`
|
||||
|
||||
const Span: FC<PropsWithChildren<{ onClick?: (e: MouseEvent) => void }>> = ({ onClick, children }) => {
|
||||
const handleClick = (e: any) => {
|
||||
e.preventDefault()
|
||||
onClick && onClick(e)
|
||||
}
|
||||
return <StyledSpan onClick={handleClick}>{children}</StyledSpan>
|
||||
}
|
||||
export const LabelWithTooltip: FC<PropsWithChildren<{ title: string; primary?: boolean }>> = ({
|
||||
primary,
|
||||
children,
|
||||
@ -24,7 +32,9 @@ export const LabelWithTooltip: FC<PropsWithChildren<{ title: string; primary?: b
|
||||
<StyledLabel>
|
||||
{children}
|
||||
<Tooltip closeable title={title} primary={primary} placement="left">
|
||||
<StyledIcon />
|
||||
<Span>
|
||||
<InfoCircleOutlined />
|
||||
</Span>
|
||||
</Tooltip>
|
||||
</StyledLabel>
|
||||
)
|
||||
|
@ -7,3 +7,4 @@ export * from './Radio'
|
||||
export * from './RadioGroup'
|
||||
export * from './ErrorMessage'
|
||||
export * from './LabelWithTooltip'
|
||||
export * from './Captcha'
|
||||
|
@ -38,6 +38,6 @@ export const Steps: FC<StepsProps> = ({ items, current = 0, ...props }) => {
|
||||
}
|
||||
})
|
||||
return (
|
||||
<StyledSteps labelPlacement="vertical" {...props} current={current} items={formattedItems} />
|
||||
<StyledSteps responsive={false} labelPlacement="vertical" {...props} current={current} items={formattedItems} />
|
||||
)
|
||||
}
|
||||
|
@ -6,14 +6,14 @@ const StyledTag = styled.div`
|
||||
background: #FFAB83;
|
||||
border-radius: 7px;
|
||||
text-align: center;
|
||||
height: 14px;
|
||||
line-height: 14px;
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
display: inline-block;
|
||||
|
||||
> span {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
top: -4px;
|
||||
line-height: 22px;
|
||||
}
|
||||
`
|
||||
export const Tag: React.FC<PropsWithChildren> = (props) => {
|
||||
|
16
src/components/Typography/Link.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import React, { FC, ComponentProps } from 'react'
|
||||
import { Typography, TypographyProps } from 'antd'
|
||||
import styled from 'styled-components'
|
||||
|
||||
type LinkProps = ComponentProps<TypographyProps['Link']> & {
|
||||
inherit?: boolean
|
||||
}
|
||||
|
||||
const StyledLink = styled(Typography.Link)<{
|
||||
$inherit?: boolean
|
||||
}>`
|
||||
${({ $inherit }) => $inherit ? 'font-size: inherit;line-height:inherit;' : ''}
|
||||
`
|
||||
export const Link: FC<LinkProps> = ({ inherit, ...props }) => {
|
||||
return <StyledLink {...props} $inherit={inherit} />
|
||||
}
|
@ -7,7 +7,6 @@ type TinyTextProps = ComponentProps<TypographyProps['Text']>
|
||||
const StyledTinyText = styled(Typography.Text)`
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
transform: scale(0.66667);
|
||||
line-height: 1.75;
|
||||
color: #1E1D1F;
|
||||
`
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { Typography } from 'antd'
|
||||
|
||||
const { Title, Link } = Typography
|
||||
const { Title } = Typography
|
||||
export * from './Paragraph'
|
||||
export * from './ButtonText'
|
||||
export * from './TinyText'
|
||||
export * from './Text'
|
||||
export { Title, Link }
|
||||
export * from './Link'
|
||||
export { Title }
|
||||
|
||||
|
2
src/constants/storage.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export const Token = 'token'
|
||||
export const UserInfo = 'user-info'
|
@ -1,3 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.7071 5.29289C21.0976 5.68342 21.0976 6.31658 20.7071 6.70711L11.4142 16L20.7071 25.2929C21.0976 25.6834 21.0976 26.3166 20.7071 26.7071C20.3166 27.0976 19.6834 27.0976 19.2929 26.7071L9.29289 16.7071C8.90237 16.3166 8.90237 15.6834 9.29289 15.2929L19.2929 5.29289C19.6834 4.90237 20.3166 4.90237 20.7071 5.29289Z" fill="#000022"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.7071 5.29289C21.0976 5.68342 21.0976 6.31658 20.7071 6.70711L11.4142 16L20.7071 25.2929C21.0976 25.6834 21.0976 26.3166 20.7071 26.7071C20.3166 27.0976 19.6834 27.0976 19.2929 26.7071L9.29289 16.7071C8.90237 16.3166 8.90237 15.6834 9.29289 15.2929L19.2929 5.29289C19.6834 4.90237 20.3166 4.90237 20.7071 5.29289Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 487 B After Width: | Height: | Size: 492 B |
@ -6,6 +6,7 @@ import { ThemeProvider } from './theme/ThemeProvider'
|
||||
import { BrowserRouter } from "react-router-dom";
|
||||
import App from './App';
|
||||
import 'typeface-lato'
|
||||
import 'typeface-roboto'
|
||||
import 'antd/dist/reset.css';
|
||||
import './styles/reset.css';
|
||||
|
||||
|
@ -1,85 +1,14 @@
|
||||
import React, { FC, useState } from 'react'
|
||||
import {
|
||||
StyledContainer,
|
||||
StyledImageWrapper,
|
||||
StyledMainContent,
|
||||
StyledHeadline,
|
||||
StyledContent,
|
||||
StyledButton,
|
||||
StyledParagraph, StyledHint, StyledFormContent,
|
||||
} from './styled'
|
||||
import { Form } from 'antd'
|
||||
import { Paragraph, Link, Text } from '../../../components/Typography'
|
||||
import { Input, ErrorMessage } from '../../../components/FormControl'
|
||||
import { ActivateGuide } from './ActivateGuide'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import React, { FC } from 'react'
|
||||
import { ActivationStore } from 'stores/ActivationStore'
|
||||
import { ActivateStarter } from './pages/activate'
|
||||
import { Creation } from './pages/creation'
|
||||
|
||||
export const Activate: FC = () => {
|
||||
const [logined] = useState(true)
|
||||
const [showModal, setModal] = useState(false)
|
||||
const handleShowModal = () => {
|
||||
setModal(true)
|
||||
}
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setModal(false)
|
||||
}
|
||||
const onFinish = (values: any) => {
|
||||
// TODO: login
|
||||
console.log('Success:', values);
|
||||
};
|
||||
export const Activate: FC = observer(() => {
|
||||
const { code } = ActivationStore.instance()
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledImageWrapper />
|
||||
<StyledMainContent>
|
||||
<StyledContent>
|
||||
{logined ? (
|
||||
<StyledHeadline level={1} $login={logined}>
|
||||
Hello, <Text inherit primary>John</Text>
|
||||
<br />
|
||||
Welcome to your kit activation.
|
||||
</StyledHeadline>
|
||||
) : (
|
||||
<StyledHeadline level={1} $login={logined}>
|
||||
Welcome to your kit <Text inherit primary>activation</Text>.
|
||||
</StyledHeadline>
|
||||
)}
|
||||
<StyledParagraph>Your kit needs to be activated before the lab can process your sample.</StyledParagraph>
|
||||
<StyledFormContent>
|
||||
<Form
|
||||
layout="vertical"
|
||||
onFinish={onFinish}
|
||||
>
|
||||
<Form.Item
|
||||
label="Activation Code"
|
||||
name="code"
|
||||
rules={[
|
||||
{
|
||||
required: true, message: <ErrorMessage message="Please input your activation code" />
|
||||
},
|
||||
// Activation code not found
|
||||
]}
|
||||
>
|
||||
<Input placeholder="xxxx-xxxx-xxxx" />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<StyledButton block htmlType="submit">Next</StyledButton>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<StyledHint>
|
||||
<Paragraph>
|
||||
{!logined ?
|
||||
<>
|
||||
Already have an account?
|
||||
<Link href="/login">Log in</Link>
|
||||
<br />
|
||||
</> : null}
|
||||
<Link onClick={handleShowModal}>Can’t find your activation code?</Link>
|
||||
</Paragraph>
|
||||
</StyledHint>
|
||||
</StyledFormContent>
|
||||
</StyledContent>
|
||||
</StyledMainContent>
|
||||
<ActivateGuide open={showModal} onClose={handleCloseModal} />
|
||||
</StyledContainer>
|
||||
<>
|
||||
{!code ? <ActivateStarter /> : <Creation />}
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { FC } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { Modal, ModalProps } from '../../../components/Modal'
|
||||
import { Title, Text, Paragraph } from '../../../components/Typography'
|
||||
import { Modal, ModalProps } from 'components/Modal'
|
||||
import { Title, Text, Paragraph } from 'components/Typography'
|
||||
|
||||
type Props = Pick<ModalProps, 'open'> & {
|
||||
onClose?: () => void
|
@ -0,0 +1,94 @@
|
||||
import React, { FC, useState } from 'react'
|
||||
import {
|
||||
StyledContainer,
|
||||
StyledImageWrapper,
|
||||
StyledMainContent,
|
||||
StyledHeadline,
|
||||
StyledContent,
|
||||
StyledButton,
|
||||
StyledParagraph, StyledHint, StyledFormContent,
|
||||
} from './styled'
|
||||
import { Form } from 'antd'
|
||||
import { Paragraph, Link, Text } from 'components/Typography'
|
||||
import { Input, ErrorMessage } from 'components/FormControl'
|
||||
import { ActivateGuide } from './ActivateGuide'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { AuthStore } from 'stores/AuthStore'
|
||||
import { ActivationStore } from 'stores/ActivationStore'
|
||||
|
||||
export const ActivateStarter: FC = observer(() => {
|
||||
const { logined } = AuthStore.instance()
|
||||
const [activationStore] = useState(ActivationStore.instance())
|
||||
const [showModal, setModal] = useState(false)
|
||||
const handleShowModal = () => {
|
||||
setModal(true)
|
||||
}
|
||||
|
||||
const handleCloseModal = () => {
|
||||
setModal(false)
|
||||
}
|
||||
const onFinish = (values: any) => {
|
||||
// TODO: active code
|
||||
console.log('Success:', values);
|
||||
activationStore.setCode(values.code)
|
||||
activationStore.setShouldCreateAccount(!logined)
|
||||
if (logined) {
|
||||
activationStore.handleNext()
|
||||
}
|
||||
};
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledImageWrapper />
|
||||
<StyledMainContent>
|
||||
<StyledContent>
|
||||
{logined ? (
|
||||
<StyledHeadline level={1} $login={logined}>
|
||||
Hello, <Text inherit primary>John</Text>
|
||||
<br />
|
||||
Welcome to your kit activation.
|
||||
</StyledHeadline>
|
||||
) : (
|
||||
<StyledHeadline level={1} $login={logined}>
|
||||
Welcome to your kit <Text inherit primary>activation</Text>.
|
||||
</StyledHeadline>
|
||||
)}
|
||||
<StyledParagraph>Your kit needs to be activated before the lab can process your sample.</StyledParagraph>
|
||||
<StyledFormContent>
|
||||
<Form
|
||||
layout="vertical"
|
||||
onFinish={onFinish}
|
||||
>
|
||||
<Form.Item
|
||||
label="Activation Code"
|
||||
name="code"
|
||||
rules={[
|
||||
{
|
||||
required: true, message: <ErrorMessage message="Please input your activation code" />
|
||||
},
|
||||
// Activation code not found
|
||||
]}
|
||||
>
|
||||
<Input placeholder="xxxx-xxxx-xxxx" />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<StyledButton block htmlType="submit">Next</StyledButton>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<StyledHint>
|
||||
<Paragraph>
|
||||
{!logined ?
|
||||
<>
|
||||
Already have an account?
|
||||
<Link href="/login">Log in</Link>
|
||||
<br />
|
||||
</> : null}
|
||||
<Link onClick={handleShowModal}>Can’t find your activation code?</Link>
|
||||
</Paragraph>
|
||||
</StyledHint>
|
||||
</StyledFormContent>
|
||||
</StyledContent>
|
||||
</StyledMainContent>
|
||||
<ActivateGuide open={showModal} onClose={handleCloseModal} />
|
||||
</StyledContainer>
|
||||
)
|
||||
})
|
Before Width: | Height: | Size: 978 KiB After Width: | Height: | Size: 978 KiB |
Before Width: | Height: | Size: 273 KiB After Width: | Height: | Size: 273 KiB |
1
src/pages/activation/activate/pages/activate/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './ActivateStarter'
|
@ -1,9 +1,8 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import Image from './images/activate@2x.png'
|
||||
import ImageMobile from './images/activateMobile@2x.png'
|
||||
import { Title, Paragraph } from '../../../components/Typography'
|
||||
import { Button } from '../../../components/Button'
|
||||
import { Title, Paragraph } from 'components/Typography'
|
||||
import { Button } from 'components/Button'
|
||||
|
||||
export const StyledContainer = styled.div`
|
||||
display: flex;
|
147
src/pages/activation/activate/pages/creation/Account.tsx
Normal file
@ -0,0 +1,147 @@
|
||||
import React, { FC, useState } from 'react'
|
||||
import { Form, Col, Row, Divider } from 'antd'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Button } from 'components/Button'
|
||||
import { Link, Paragraph, Title } from 'components/Typography'
|
||||
import { ErrorMessage, Input, LabelWithTooltip, Password } from 'components/FormControl'
|
||||
import { StyledAccountForm, StyledAccountWrap, StyledHelp, StyledSubmitButton } from './styled'
|
||||
import { ActivationStore } from 'stores/ActivationStore'
|
||||
import { VerifyModal } from './VerifyModal'
|
||||
import styled from 'styled-components'
|
||||
import { Breakpoint } from '../../../../../components/Breakpoint'
|
||||
|
||||
const StyledTitle = styled(Title)`
|
||||
${props => props.theme.breakpoints.up('s')} {
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
`
|
||||
export const Account: FC = observer(() => {
|
||||
const [store] = useState(ActivationStore.instance())
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const handleFinish = (values: any) => {
|
||||
// TODO: verify email
|
||||
console.log(values)
|
||||
setShowModal(true)
|
||||
}
|
||||
const handleClose = (success: boolean) => {
|
||||
setShowModal(false)
|
||||
if (success) {
|
||||
// TODO: register account
|
||||
store.handleNext()
|
||||
}
|
||||
}
|
||||
return (
|
||||
<StyledAccountWrap>
|
||||
<StyledTitle level={2}>Create an account to activate your kit</StyledTitle>
|
||||
<Paragraph style={{ marginBottom: 0 }}>If you already have an account, <Link inherit href="/login">log
|
||||
in</Link> to continue.</Paragraph>
|
||||
<StyledAccountForm layout="vertical" onFinish={handleFinish}>
|
||||
<Row gutter={12}>
|
||||
<Col span={12}>
|
||||
<Form.Item name="firstName" label="First Name"
|
||||
rules={[
|
||||
{
|
||||
required: true, message: <ErrorMessage message="Please input your first name" />
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item name="lastName" label="Last Name" rules={[
|
||||
{
|
||||
required: true, message: <ErrorMessage message="Please input your last name" />
|
||||
},
|
||||
]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<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="Confirm Email"
|
||||
name="repeatEmail"
|
||||
dependencies={['email']}
|
||||
rules={[
|
||||
{
|
||||
required: true, message: <ErrorMessage message="Please input your confirm email" />
|
||||
},
|
||||
{ type: 'email', message: <ErrorMessage message="Invalid email" /> },
|
||||
({ getFieldValue }) => ({
|
||||
validator(_, value) {
|
||||
if (!value || getFieldValue('email') === value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error('The two email that you entered do not match'));
|
||||
},
|
||||
}),
|
||||
]}
|
||||
>
|
||||
<Input placeholder="email@example.com" />
|
||||
</Form.Item>
|
||||
<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'));
|
||||
},
|
||||
}),
|
||||
]}
|
||||
extra={<StyledHelp>By clicking "Next" you acknowledge the <Link inherit>Privacy Policy</Link> and agree to
|
||||
our <Link inherit>Terms of Use</Link>.</StyledHelp>}
|
||||
>
|
||||
<Password placeholder="********" />
|
||||
</Form.Item>
|
||||
<Form.Item style={{ textAlign: 'center', marginTop: 28 }}>
|
||||
<StyledSubmitButton htmlType="submit">Next</StyledSubmitButton>
|
||||
</Form.Item>
|
||||
</StyledAccountForm>
|
||||
<Breakpoint s up>
|
||||
<div style={{ textAlign: "center", marginTop: 30 }}>
|
||||
<Link style={{ padding: '0 17px' }}>Disclaimer</Link>
|
||||
<Divider type="vertical" />
|
||||
<Link style={{ padding: '0 17px' }}>Privacy</Link>
|
||||
</div>
|
||||
</Breakpoint>
|
||||
<VerifyModal open={showModal} onClose={handleClose} />
|
||||
</StyledAccountWrap>
|
||||
)
|
||||
})
|
@ -0,0 +1,44 @@
|
||||
import React, { ChangeEvent, FC } from 'react'
|
||||
import { data } from './caData'
|
||||
import { SelectProps, Select, Input } from 'components/FormControl'
|
||||
import { Col, Row } from 'antd'
|
||||
|
||||
export const CountrySelect: FC<SelectProps> = ({ style, ...props }) => {
|
||||
return (
|
||||
<Select
|
||||
placeholder="CA"
|
||||
{...props}
|
||||
style={{ width: 93, ...style }}
|
||||
options={data.map(label => ({ label, value: label }))}
|
||||
dropdownStyle={{ width: 350 }}
|
||||
dropdownMatchSelectWidth={350}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
type CityCountryValue = { city?: string, country: string | null }
|
||||
|
||||
export interface CityCountrySelectProps {
|
||||
value?: CityCountryValue
|
||||
onChange?: (value: CityCountryValue) => void
|
||||
}
|
||||
|
||||
export const CityCountrySelect: FC<CityCountrySelectProps> = ({ value = { city: '', country: null }, onChange }) => {
|
||||
const handleChangeCity = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
onChange && onChange({ ...value, city: e.target.value })
|
||||
}
|
||||
const handleChangeCountry = (country: string) => {
|
||||
onChange && onChange({ ...value, country })
|
||||
}
|
||||
return (
|
||||
<Row gutter={22}>
|
||||
<Col flex="auto">
|
||||
<Input value={value.city} onChange={handleChangeCity} />
|
||||
</Col>
|
||||
<Col flex="none">
|
||||
<CountrySelect value={value.country} onChange={handleChangeCountry} />
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
38
src/pages/activation/activate/pages/creation/Creation.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import React, { FC } from 'react'
|
||||
import {
|
||||
StyledBar,
|
||||
StyledContainer, StyledMain, StyledParagraph, StyledStepItemContainer, StyledSteps,
|
||||
} from './styled'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { ActivationStore } from 'stores/ActivationStore'
|
||||
import { Account } from './Account'
|
||||
import { TesterInfo } from './TesterInfo'
|
||||
import { Done } from './Done'
|
||||
import { Review } from './Review'
|
||||
import { Breakpoint } from '../../../../../components/Breakpoint'
|
||||
|
||||
export const Creation: FC = observer(() => {
|
||||
const { code, step, stepItems } = ActivationStore.instance()
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledMain>
|
||||
<StyledSteps
|
||||
current={step}
|
||||
items={stepItems} />
|
||||
<Breakpoint s down>
|
||||
<StyledBar />
|
||||
</Breakpoint>
|
||||
<StyledStepItemContainer>
|
||||
{step !== 3 ?
|
||||
<StyledParagraph>You are activating your iHealth CheckMeSafe Home Collection Kit Critical 2 (HIV &
|
||||
Syphilis),
|
||||
ID {code}</StyledParagraph> : null}
|
||||
{step === 0 ? <Account /> : null}
|
||||
{step === 1 ? <TesterInfo /> : null}
|
||||
{step === 2 ? <Review /> : null}
|
||||
{step === 3 ? <Done /> : null}
|
||||
</StyledStepItemContainer>
|
||||
</StyledMain>
|
||||
</StyledContainer>
|
||||
)
|
||||
})
|
72
src/pages/activation/activate/pages/creation/Done.tsx
Normal file
@ -0,0 +1,72 @@
|
||||
import React, { FC, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Link, Text, Title } from 'components/Typography'
|
||||
import { ActivationStore } from 'stores/ActivationStore'
|
||||
import styled from 'styled-components'
|
||||
import { ReactComponent as SuccessSvg } from './images/success.svg'
|
||||
|
||||
const StyledLinkItem = styled.div`
|
||||
margin-bottom: 6px;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 12px;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledImageWrapper = styled.div`
|
||||
position: relative;
|
||||
height: 231px;
|
||||
text-align: center;
|
||||
|
||||
${props => props.theme.breakpoints.up('s')} {
|
||||
height: 316px;
|
||||
text-align: left;
|
||||
padding-left: 103px;
|
||||
}
|
||||
|
||||
> svg {
|
||||
width: 223px;
|
||||
height: 231px;
|
||||
|
||||
${props => props.theme.breakpoints.up('s')} {
|
||||
width: 301px;
|
||||
height: 385px;
|
||||
position: absolute;
|
||||
top: -15px;
|
||||
}
|
||||
}
|
||||
`
|
||||
const StyledTitle = styled(Title)`
|
||||
font-size: 36px;
|
||||
margin-bottom: 7px !important;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
`
|
||||
export const Done: FC = observer(() => {
|
||||
const [store] = useState(ActivationStore.instance())
|
||||
const { code } = store
|
||||
return (
|
||||
<>
|
||||
<StyledTitle>Your kit is <Text primary inherit>activated!</Text></StyledTitle>
|
||||
<Title level={2}>You have just activated your iHealth CheckmeSafe Home Collection Kit Critical 2 - HIV & Syphilis
|
||||
ID {code}</Title>
|
||||
<StyledImageWrapper>
|
||||
<SuccessSvg />
|
||||
</StyledImageWrapper>
|
||||
<Title level={2}>What’s Next?</Title>
|
||||
<div>
|
||||
<StyledLinkItem>
|
||||
<Link>Watch a video on how to collect your sample</Link>
|
||||
</StyledLinkItem>
|
||||
<StyledLinkItem>
|
||||
<Link href="/dashboard">Go to my dashboard</Link>
|
||||
</StyledLinkItem>
|
||||
<StyledLinkItem>
|
||||
<Link>View FAQs</Link>
|
||||
</StyledLinkItem>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
})
|
@ -0,0 +1,24 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Select, SelectProps } from 'components/FormControl'
|
||||
|
||||
|
||||
export const EthnicitySelect: FC<SelectProps> = (props) => {
|
||||
return (
|
||||
<Select {...props}
|
||||
options={[
|
||||
{
|
||||
"label": "Hispanic/Latino",
|
||||
"value": "Hispanic/Latino"
|
||||
},
|
||||
{
|
||||
"label": "Non-Hispanic/non-Latino",
|
||||
"value": "Non-Hispanic/non-Latino"
|
||||
},
|
||||
{
|
||||
"label": "Unknown",
|
||||
"value": "Unknown"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
36
src/pages/activation/activate/pages/creation/RaceSelect.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import React, { FC } from 'react'
|
||||
import { Select, SelectProps } from 'components/FormControl'
|
||||
|
||||
|
||||
export const RaceSelect: FC<SelectProps> = (props) => {
|
||||
return (
|
||||
<Select {...props}
|
||||
options={[
|
||||
{
|
||||
"label": "African-American/Black",
|
||||
"value": "African-American/Black"
|
||||
},
|
||||
{
|
||||
"label": "American Indian/Alaska Native",
|
||||
"value": "American Indian/Alaska Native"
|
||||
},
|
||||
{
|
||||
"label": "Asian",
|
||||
"value": "Asian"
|
||||
},
|
||||
{
|
||||
"label": "Pacific Islander",
|
||||
"value": "Pacific Islander"
|
||||
},
|
||||
{
|
||||
"label": "White",
|
||||
"value": "White"
|
||||
},
|
||||
{
|
||||
"label": "Other/Unknown",
|
||||
"value": "Other/Unknown"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
101
src/pages/activation/activate/pages/creation/Review.tsx
Normal file
@ -0,0 +1,101 @@
|
||||
import React, { FC, PropsWithChildren, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Paragraph, Text, Title } from 'components/Typography'
|
||||
import { AuthStore } from 'stores/AuthStore'
|
||||
import { Col, Row } from 'antd'
|
||||
import styled from 'styled-components'
|
||||
import { ActivationStore } from 'stores/ActivationStore'
|
||||
import { Button } from 'components/Button'
|
||||
import Icon from '@ant-design/icons'
|
||||
import { ReactComponent as CaretLeft } from 'icons/caretLeft.svg'
|
||||
|
||||
const StyledTitle = styled(Title)`
|
||||
font-size: 36px;
|
||||
margin-bottom: 11px !important;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledParagraph = styled(Paragraph)`
|
||||
margin-bottom: 30px !important;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
margin-bottom: 28px !important;
|
||||
}
|
||||
`
|
||||
const StyledRow = styled(Row)`
|
||||
margin-top: 147px;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
justify-content: center;
|
||||
margin-top: 36px;
|
||||
}
|
||||
`
|
||||
|
||||
const StyleButton = styled(Button)`
|
||||
width: 358px;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
width: 150px;
|
||||
}
|
||||
`
|
||||
const StyledItem = styled.div`
|
||||
font-size: 16px;
|
||||
margin-bottom: 10px;
|
||||
`
|
||||
const StyledLabel = styled.div`
|
||||
font-weight: 700;
|
||||
margin-bottom: 4px;
|
||||
line-height: 18px
|
||||
`
|
||||
const StyledValue = styled(Paragraph)``
|
||||
const ReviewItem: FC<PropsWithChildren<{ label: string }>> = ({ label, children }) => {
|
||||
return (
|
||||
<StyledItem>
|
||||
<StyledLabel>{label}</StyledLabel>
|
||||
<StyledValue>{children}</StyledValue>
|
||||
</StyledItem>
|
||||
)
|
||||
}
|
||||
export const Review: FC = observer(() => {
|
||||
const { userName, userInfo } = AuthStore.instance()
|
||||
const [store] = useState(ActivationStore.instance())
|
||||
const { testerInfo } = store
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledTitle>Hello, <Text primary inherit>{userName}</Text></StyledTitle>
|
||||
<StyledParagraph style={{ marginBottom: 30 }}>Please review the tester’s information below.</StyledParagraph>
|
||||
<Row gutter={22}>
|
||||
<Col flex="none">
|
||||
<ReviewItem label="First Name">{testerInfo.firstName}</ReviewItem>
|
||||
</Col>
|
||||
<Col flex="none">
|
||||
<ReviewItem label="Last Name">{testerInfo.lastName}</ReviewItem>
|
||||
</Col>
|
||||
</Row>
|
||||
<ReviewItem label="Sex assigned at birth?">{testerInfo.gender === 'female' ? 'Female' : 'Male'}</ReviewItem>
|
||||
<ReviewItem label="Race">{testerInfo.race}</ReviewItem>
|
||||
<ReviewItem label="Ethnicity">{testerInfo.ethnicity}</ReviewItem>
|
||||
<ReviewItem label="Phone Number">{testerInfo.phone}</ReviewItem>
|
||||
<ReviewItem
|
||||
label="Address">{[testerInfo.address, testerInfo.address2, testerInfo.city + ', ' + testerInfo.ca, testerInfo.postal].filter(Boolean).map((text, index) => (
|
||||
<div key={index}>{text}</div>
|
||||
))}</ReviewItem>
|
||||
<ReviewItem label="Email(s) to be notified">
|
||||
<div>{testerInfo.email || userInfo?.email}</div>
|
||||
{(testerInfo.additionalEmails || []).map((e: string, index: number) => <div key={index}>{e}</div>)}
|
||||
</ReviewItem>
|
||||
<StyledRow align="middle" gutter={18} wrap={false} >
|
||||
<Col flex="none">
|
||||
<Button icon={<Icon component={CaretLeft} />} onClick={store.handlePrev.bind(store)} />
|
||||
</Col>
|
||||
<Col flex="none">
|
||||
<StyleButton onClick={store.handleNext.bind(store)}>Submit</StyleButton>
|
||||
</Col>
|
||||
</StyledRow>
|
||||
</>
|
||||
)
|
||||
})
|
299
src/pages/activation/activate/pages/creation/TesterInfo.tsx
Normal file
@ -0,0 +1,299 @@
|
||||
import React, { FC, useState } from 'react'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Paragraph, Text, Title } from 'components/Typography'
|
||||
import { Col, Form, Row, Space } from 'antd'
|
||||
import { Button } from 'components/Button'
|
||||
import { AuthStore } from 'stores/AuthStore'
|
||||
import { ErrorMessage, Input, Radio, RadioGroup, Checkbox } from 'components/FormControl'
|
||||
import { RadioChangeEvent } from 'antd/es/radio/interface'
|
||||
import styled from 'styled-components'
|
||||
import { ReactComponent as CaretLeft } from 'icons/caretLeft.svg'
|
||||
import Icon, { PlusOutlined, MinusOutlined } from '@ant-design/icons'
|
||||
import { RaceSelect } from './RaceSelect'
|
||||
import { EthnicitySelect } from './EthnicitySelect'
|
||||
import { CountrySelect } from './CityCountrySelect'
|
||||
import { data } from './caData'
|
||||
import { ActivationStore } from 'stores/ActivationStore'
|
||||
import { StyledSubmitButton } from './styled'
|
||||
|
||||
const StyledRadio = styled(Radio)`
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
.ant-radio-checked .ant-radio-inner::after {
|
||||
background: ${props => props.theme.brandPrimary};
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const StyledRadioDiv = styled.div`
|
||||
display: inline-block;
|
||||
padding: 0 20px;
|
||||
width: 122px;
|
||||
`
|
||||
const StyledTitle = styled(Title)`
|
||||
margin-bottom: 28px !important;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
margin-top: 13px;
|
||||
margin-bottom: 12px !important;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledButtonWrapper = styled.div`
|
||||
text-align: center;
|
||||
margin-top: 80px;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
margin-top: 30px;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledFormTitle = styled(Title)`
|
||||
font-size: 36px;
|
||||
margin-bottom: 11px !important;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledForm = styled(Form)`
|
||||
margin-top: 23px;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
margin-top: 13px;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledFormButtonWrapper = styled(Form.Item)`
|
||||
margin-top: 65px;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
margin-top: 136px;
|
||||
|
||||
.ant-row {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const StyleFormButton = styled(Button)`
|
||||
width: 358px;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
width: 150px;
|
||||
}
|
||||
`
|
||||
|
||||
export const TesterInfo: FC = observer(() => {
|
||||
const [form] = Form.useForm()
|
||||
const firstName = Form.useWatch('firstName', form)
|
||||
const lastName = Form.useWatch('lastName', form)
|
||||
const { userName, userInfo } = AuthStore.instance()
|
||||
const [store] = useState(ActivationStore.instance())
|
||||
const [forMe, setForMe] = useState(1)
|
||||
const [innerStep, setInnerStep] = useState(0)
|
||||
const handleForMe = (e: RadioChangeEvent) => {
|
||||
setForMe(e.target.value)
|
||||
}
|
||||
const handleInnerNext = () => {
|
||||
setInnerStep(1)
|
||||
}
|
||||
const handleInnerPrev = () => {
|
||||
setInnerStep(0)
|
||||
form.resetFields()
|
||||
}
|
||||
const handleFinish = (values: any) => {
|
||||
console.log(values)
|
||||
// TODO: setup values
|
||||
store.setTesterInfo(values)
|
||||
store.handleNext()
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
{innerStep === 0 ? (
|
||||
<>
|
||||
<StyledTitle level={2}>You have successfully verified your email address.</StyledTitle>
|
||||
<Paragraph style={{ marginBottom: 30 }}>Who will be taking the test?</Paragraph>
|
||||
<RadioGroup value={forMe} onChange={handleForMe}>
|
||||
<Space direction="vertical">
|
||||
<StyledRadio value={1}>I, {userName}, will take the
|
||||
test.</StyledRadio>
|
||||
<StyledRadio value={0}>This test is for someone else. </StyledRadio>
|
||||
</Space>
|
||||
</RadioGroup>
|
||||
<StyledButtonWrapper>
|
||||
<StyledSubmitButton onClick={handleInnerNext}>Next</StyledSubmitButton>
|
||||
</StyledButtonWrapper>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<StyledFormTitle>Hello, <Text primary inherit>{userName}</Text></StyledFormTitle>
|
||||
<Paragraph>Please provide information of <b>the person being tested</b>.</Paragraph>
|
||||
<StyledForm layout="vertical"
|
||||
onFinish={handleFinish} form={form}
|
||||
initialValues={{
|
||||
gender: 'female',
|
||||
firstName: userInfo?.firstName,
|
||||
lastName: userInfo?.lastName,
|
||||
additionalEmails: [""],
|
||||
ca: data[0]
|
||||
}}>
|
||||
<Row gutter={12}>
|
||||
<Col span={12}>
|
||||
<Form.Item label="First Name" name="firstName"
|
||||
rules={[
|
||||
{
|
||||
required: true, message: <ErrorMessage message="Please input your first name" />
|
||||
},
|
||||
]}>
|
||||
<Input readOnly={Boolean(forMe)} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={12}>
|
||||
<Form.Item name="lastName" label="Last Name" rules={[
|
||||
{
|
||||
required: true, message: <ErrorMessage message="Please input your last name" />
|
||||
},
|
||||
]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Item name="gender" label={<div>
|
||||
<Paragraph>Sex assigned at birth?</Paragraph>
|
||||
<Paragraph italic style={{ fontWeight: 400, fontFamily: 'Roboto' }}>
|
||||
We need to know your sex at birth as the lab requires this information to provide the correct reference
|
||||
range for your results.</Paragraph>
|
||||
</div>}
|
||||
rules={[
|
||||
{
|
||||
required: true, message: <ErrorMessage message="Please select your gender" />
|
||||
},
|
||||
]}
|
||||
>
|
||||
<RadioGroup>
|
||||
<Radio value="female"><StyledRadioDiv>Female</StyledRadioDiv></Radio>
|
||||
<Radio value="male"><StyledRadioDiv>Male</StyledRadioDiv></Radio>
|
||||
</RadioGroup>
|
||||
</Form.Item>
|
||||
<Form.Item label="Race" name="race" rules={[
|
||||
{
|
||||
required: true, message: <ErrorMessage message="Please select your race" />
|
||||
},
|
||||
]}>
|
||||
<RaceSelect />
|
||||
</Form.Item>
|
||||
<Form.Item label="Ethnicity" name="ethnicity"
|
||||
rules={[
|
||||
{
|
||||
required: true, message: <ErrorMessage message="Please input your ethnicity" />
|
||||
},
|
||||
]}
|
||||
>
|
||||
<EthnicitySelect />
|
||||
</Form.Item>
|
||||
<Form.Item label="Phone Number" name="phone"
|
||||
rules={[
|
||||
{
|
||||
required: true, message: <ErrorMessage message="Please input your phone number" />
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input placeholder="(xxx) - xxx - xxxx" />
|
||||
</Form.Item>
|
||||
<Form.Item label="Address" name="address" rules={[
|
||||
{
|
||||
required: true, message: <ErrorMessage message="Please input your address" />
|
||||
},
|
||||
]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="Address line2" name="address2"><Input /></Form.Item>
|
||||
<Row gutter={22}>
|
||||
<Col flex="auto">
|
||||
<Form.Item label="City" name="city" rules={[
|
||||
{
|
||||
required: true, message: <ErrorMessage message="Please input your city" />
|
||||
},
|
||||
]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col flex="none">
|
||||
<Form.Item name="ca" label=" ">
|
||||
<CountrySelect />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Form.Item label="Postal / Zip Code" name="postal" rules={[
|
||||
{
|
||||
required: true, message: <ErrorMessage message="Please input your postal/zip code" />
|
||||
},
|
||||
]}><Input /></Form.Item>
|
||||
<Form.List name="additionalEmails">
|
||||
{(fields, { add, remove }, { errors }) => (
|
||||
<>
|
||||
{fields.map((field, index) => (
|
||||
<Row gutter={22} align="bottom" wrap={false}>
|
||||
<Col flex="auto">
|
||||
<Form.Item {...field}
|
||||
rules={[
|
||||
{ type: 'email', message: <ErrorMessage message="Invalid email" /> },
|
||||
]}
|
||||
label={index === 0 ? "Send results notifications to additional emails. (Optional)" : ""}
|
||||
key={field.key}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col flex="none">
|
||||
<Form.Item label={index === 0 ? " " : ""}>
|
||||
{index === fields.length - 1 &&
|
||||
<Button
|
||||
onClick={() => add()}
|
||||
icon={<PlusOutlined />}
|
||||
/>}
|
||||
{index < fields.length - 1 &&
|
||||
<Button
|
||||
onClick={() => remove(index)}
|
||||
icon={<MinusOutlined />}
|
||||
/>}
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
<Form.ErrorList errors={errors} />
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
{!forMe &&
|
||||
<Form.Item name="checked" valuePropName="checked" rules={[{
|
||||
required: true,
|
||||
validateTrigger: 'onSubmit',
|
||||
transform: value => value ? 'y' : '',
|
||||
message: <ErrorMessage message="Please accept agreement" />
|
||||
}]}>
|
||||
<Checkbox>
|
||||
By checking this box, I indicate that I {userName}, have
|
||||
obtained {firstName || lastName ? [firstName, lastName].filter(Boolean).join(' ') : '[Tester\'s Name]'}'s
|
||||
consent
|
||||
to assist him/her in the sample collection process and fill out any necessary forms on his/her behalf.
|
||||
</Checkbox>
|
||||
</Form.Item>
|
||||
}
|
||||
<StyledFormButtonWrapper>
|
||||
<Row align="middle" gutter={18} wrap={false}>
|
||||
<Col flex="none">
|
||||
<Button icon={<Icon component={CaretLeft} />} onClick={handleInnerPrev} />
|
||||
</Col>
|
||||
<Col flex="none">
|
||||
<StyleFormButton htmlType="submit">Next</StyleFormButton>
|
||||
</Col>
|
||||
</Row>
|
||||
</StyledFormButtonWrapper>
|
||||
</StyledForm>
|
||||
</>
|
||||
)}
|
||||
|
||||
</div>
|
||||
)
|
||||
})
|
140
src/pages/activation/activate/pages/creation/VerifyModal.tsx
Normal file
@ -0,0 +1,140 @@
|
||||
import React, { FC } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { Captcha, ErrorMessage } from 'components/FormControl'
|
||||
import { Modal, ModalProps } from 'components/Modal'
|
||||
import { Title, Paragraph, Text } from 'components/Typography'
|
||||
import { ReactComponent as EmailSvg } from './images/email.svg'
|
||||
import { ReactComponent as EmailWarningSvg } from './images/emailWarning.svg'
|
||||
import { Form } from 'antd'
|
||||
import { Button } from 'components/Button'
|
||||
import { Breakpoint } from 'components/Breakpoint'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { AuthStore } from 'stores/AuthStore'
|
||||
|
||||
type Props = Pick<ModalProps, 'open'> & {
|
||||
onClose?: (success: boolean) => void
|
||||
}
|
||||
|
||||
const StyledContent = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
height: 366px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
`
|
||||
const StyledImageWrapper = styled.div`
|
||||
flex: 0 0 250px;
|
||||
|
||||
> svg {
|
||||
width: 250px;
|
||||
height: 235px;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledFormWrapper = styled.div`
|
||||
flex: 1;
|
||||
padding-left: 44px;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
padding-left: 5px;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledFormTitle = styled(Title)`
|
||||
${props => props.theme.breakpoints.up('s')} {
|
||||
margin-bottom: 4px !important;
|
||||
}
|
||||
`
|
||||
const StyledCodeFormItem = styled(Form.Item)`
|
||||
padding: 28px 0;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
padding: 40px 0;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledRegisterTitle = styled(Title)`
|
||||
${props => props.theme.breakpoints.up('s')} {
|
||||
margin-bottom: 28px !important;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledParagraph = styled(Paragraph)`
|
||||
margin-bottom: 53px !important;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
margin-bottom: 9px !important;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledRegisterImageWrapper = styled.div`
|
||||
text-align: center;
|
||||
margin-bottom: 19px;
|
||||
> svg {
|
||||
width: 188px;
|
||||
height: 176px;
|
||||
}
|
||||
`
|
||||
|
||||
export const VerifyModal: FC<Props> = observer(({ onClose, ...props }) => {
|
||||
const { userInfo } = AuthStore.instance()
|
||||
const [expried, setExpried] = React.useState('9:45')
|
||||
const [hasRegistered, setHasRegistered] = React.useState(false)
|
||||
const handleSendCode = () => {}
|
||||
const handleClose = (success: boolean) => () => {
|
||||
onClose && onClose(success)
|
||||
}
|
||||
const handleFinish = ({ code }: { code: string }) => {
|
||||
console.log(code)
|
||||
// TODO: 验证
|
||||
handleClose(true)()
|
||||
}
|
||||
return (
|
||||
<Modal {...props} onCancel={handleClose(false)} width={688}>
|
||||
<StyledContent>
|
||||
<Breakpoint s up>
|
||||
<StyledImageWrapper>
|
||||
<EmailSvg />
|
||||
</StyledImageWrapper>
|
||||
</Breakpoint>
|
||||
<StyledFormWrapper>
|
||||
{hasRegistered ? (
|
||||
<>
|
||||
<Breakpoint s down>
|
||||
<StyledRegisterImageWrapper>
|
||||
<EmailWarningSvg />
|
||||
</StyledRegisterImageWrapper>
|
||||
</Breakpoint>
|
||||
<StyledRegisterTitle level={2}>Email address is already registered</StyledRegisterTitle>
|
||||
<StyledParagraph>Please login to your existing account.</StyledParagraph>
|
||||
<Button htmlType="submit" block href="/login">Log in</Button>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<StyledFormTitle level={2}>Verify your email</StyledFormTitle>
|
||||
<Paragraph>A 6-digit code has been sent to {userInfo?.email}</Paragraph>
|
||||
<Form layout="vertical" onFinish={handleFinish}>
|
||||
<StyledCodeFormItem name="code" rules={[
|
||||
{
|
||||
required: true, message: <ErrorMessage message="Please input your verification code" />
|
||||
},
|
||||
{
|
||||
len: 6,
|
||||
validateTrigger: 'onSubmit',
|
||||
message: <ErrorMessage message="The verification code you entered is invalid." />
|
||||
},
|
||||
]}>
|
||||
<Captcha />
|
||||
</StyledCodeFormItem>
|
||||
<Paragraph>The code will expire in {expried}</Paragraph>
|
||||
<Paragraph>Didn’t receive the code? <Text primary inherit onClick={handleSendCode}>Send
|
||||
again</Text></Paragraph>
|
||||
<Form.Item style={{ marginTop: 16 }}>
|
||||
<Button htmlType="submit" block>Verify</Button>
|
||||
</Form.Item>
|
||||
</Form></>)}
|
||||
</StyledFormWrapper>
|
||||
</StyledContent>
|
||||
</Modal>
|
||||
)
|
||||
})
|
240
src/pages/activation/activate/pages/creation/caData.ts
Normal file
@ -0,0 +1,240 @@
|
||||
export const data = [
|
||||
"United States",
|
||||
"Afghanistan",
|
||||
"Åland Islands",
|
||||
"Albania",
|
||||
"Algeria",
|
||||
"Andorra",
|
||||
"Angola",
|
||||
"Anguilla",
|
||||
"Antigua & Barbuda",
|
||||
"Argentina",
|
||||
"Armenia",
|
||||
"Aruba",
|
||||
"Ascension Island",
|
||||
"Australia",
|
||||
"Austria",
|
||||
"Azerbaijan",
|
||||
"Bahamas",
|
||||
"Bahrain",
|
||||
"Bangladesh",
|
||||
"Barbados",
|
||||
"Belarus",
|
||||
"Belgium",
|
||||
"Belize",
|
||||
"Benin",
|
||||
"Bermuda",
|
||||
"Bhutan",
|
||||
"Bolivia",
|
||||
"Bosnia & Herzegovina",
|
||||
"Botswana",
|
||||
"Brazil",
|
||||
"British Indian Ocean Territory",
|
||||
"British Virgin Islands",
|
||||
"Brunei",
|
||||
"Bulgaria",
|
||||
"Burkina Faso",
|
||||
"Burundi",
|
||||
"Cambodia",
|
||||
"Cameroon",
|
||||
"Canada",
|
||||
"Cape Verde",
|
||||
"Caribbean Netherlands",
|
||||
"Cayman Islands",
|
||||
"Central African Republic",
|
||||
"Chad",
|
||||
"Chile",
|
||||
"China",
|
||||
"Christmas Island",
|
||||
"Cocos (Keeling) Islands",
|
||||
"Colombia",
|
||||
"Comoros",
|
||||
"Congo - Brazzaville",
|
||||
"Congo - Kinshasa",
|
||||
"Cook Islands",
|
||||
"Costa Rica",
|
||||
"Croatia",
|
||||
"Curaçao",
|
||||
"Cyprus",
|
||||
"Czechia",
|
||||
"Côte d’Ivoire",
|
||||
"Denmark",
|
||||
"Djibouti",
|
||||
"Dominica",
|
||||
"Dominican Republic",
|
||||
"Ecuador",
|
||||
"Egypt",
|
||||
"El Salvador",
|
||||
"Equatorial Guinea",
|
||||
"Eritrea",
|
||||
"Estonia",
|
||||
"Eswatini",
|
||||
"Ethiopia",
|
||||
"Falkland Islands",
|
||||
"Faroe Islands",
|
||||
"Fiji",
|
||||
"Finland",
|
||||
"France",
|
||||
"French Guiana",
|
||||
"French Polynesia",
|
||||
"French Southern Territories",
|
||||
"Gabon",
|
||||
"Gambia",
|
||||
"Georgia",
|
||||
"Germany",
|
||||
"Ghana",
|
||||
"Gibraltar",
|
||||
"Greece",
|
||||
"Greenland",
|
||||
"Grenada",
|
||||
"Guadeloupe",
|
||||
"Guatemala",
|
||||
"Guernsey",
|
||||
"Guinea",
|
||||
"Guinea-Bissau",
|
||||
"Guyana",
|
||||
"Haiti",
|
||||
"Honduras",
|
||||
"Hong Kong SAR",
|
||||
"Hungary",
|
||||
"Iceland",
|
||||
"India",
|
||||
"Indonesia",
|
||||
"Iraq",
|
||||
"Ireland",
|
||||
"Isle of Man",
|
||||
"Israel",
|
||||
"Italy",
|
||||
"Jamaica",
|
||||
"Japan",
|
||||
"Jersey",
|
||||
"Jordan",
|
||||
"Kazakhstan",
|
||||
"Kenya",
|
||||
"Kiribati",
|
||||
"Kosovo",
|
||||
"Kuwait",
|
||||
"Kyrgyzstan",
|
||||
"Laos",
|
||||
"Latvia",
|
||||
"Lebanon",
|
||||
"Lesotho",
|
||||
"Liberia",
|
||||
"Libya",
|
||||
"Liechtenstein",
|
||||
"Lithuania",
|
||||
"Luxembourg",
|
||||
"Macao SAR",
|
||||
"Madagascar",
|
||||
"Malawi",
|
||||
"Malaysia",
|
||||
"Maldives",
|
||||
"Mali",
|
||||
"Malta",
|
||||
"Martinique",
|
||||
"Mauritania",
|
||||
"Mauritius",
|
||||
"Mayotte",
|
||||
"Mexico",
|
||||
"Moldova",
|
||||
"Monaco",
|
||||
"Mongolia",
|
||||
"Montenegro",
|
||||
"Montserrat",
|
||||
"Morocco",
|
||||
"Mozambique",
|
||||
"Myanmar (Burma)",
|
||||
"Namibia",
|
||||
"Nauru",
|
||||
"Nepal",
|
||||
"Netherlands",
|
||||
"New Caledonia",
|
||||
"New Zealand",
|
||||
"Nicaragua",
|
||||
"Niger",
|
||||
"Nigeria",
|
||||
"Niue",
|
||||
"Norfolk Island",
|
||||
"North Macedonia",
|
||||
"Norway",
|
||||
"Oman",
|
||||
"Pakistan",
|
||||
"Palestinian Territories",
|
||||
"Panama",
|
||||
"Papua New Guinea",
|
||||
"Paraguay",
|
||||
"Peru",
|
||||
"Philippines",
|
||||
"Pitcairn Islands",
|
||||
"Poland",
|
||||
"Portugal",
|
||||
"Qatar",
|
||||
"Réunion",
|
||||
"Romania",
|
||||
"Russia",
|
||||
"Rwanda",
|
||||
"Samoa",
|
||||
"San Marino",
|
||||
"São Tomé & Príncipe",
|
||||
"Saudi Arabia",
|
||||
"Senegal",
|
||||
"Serbia",
|
||||
"Seychelles",
|
||||
"Sierra Leone",
|
||||
"Singapore",
|
||||
"Sint Maarten",
|
||||
"Slovakia",
|
||||
"Slovenia",
|
||||
"Solomon Islands",
|
||||
"Somalia",
|
||||
"South Africa",
|
||||
"South Georgia & South Sandwich Islands",
|
||||
"South Korea",
|
||||
"South Sudan",
|
||||
"Spain",
|
||||
"Sri Lanka",
|
||||
"St. Barthélemy",
|
||||
"St. Helena",
|
||||
"St. Kitts & Nevis",
|
||||
"St. Lucia",
|
||||
"St. Martin",
|
||||
"St. Pierre & Miquelon",
|
||||
"St. Vincent & Grenadines",
|
||||
"Sudan",
|
||||
"Suriname",
|
||||
"Svalbard & Jan Mayen",
|
||||
"Sweden",
|
||||
"Switzerland",
|
||||
"Taiwan",
|
||||
"Tajikistan",
|
||||
"Tanzania",
|
||||
"Thailand",
|
||||
"Timor-Leste",
|
||||
"Togo",
|
||||
"Tokelau",
|
||||
"Tonga",
|
||||
"Trinidad & Tobago",
|
||||
"Tristan da Cunha",
|
||||
"Tunisia",
|
||||
"Turkey",
|
||||
"Turkmenistan",
|
||||
"Turks & Caicos Islands",
|
||||
"Tuvalu",
|
||||
"U.S. Outlying Islands",
|
||||
"Uganda",
|
||||
"Ukraine",
|
||||
"United Arab Emirates",
|
||||
"United Kingdom",
|
||||
"United States",
|
||||
"Uruguay",
|
||||
"Uzbekistan",
|
||||
"Vanuatu",
|
||||
"Vatican City",
|
||||
"Venezuela",
|
||||
"Vietnam",
|
||||
"Wallis & Futuna",
|
||||
"Western Sahara",
|
||||
"Yemen",
|
||||
"Zambia",
|
||||
"Zimbabwe"
|
||||
]
|
@ -0,0 +1,16 @@
|
||||
<svg width="250" height="235" viewBox="0 0 250 235" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="22" y="81" width="205" height="125" rx="7" fill="#FF712E"/>
|
||||
<path d="M122.988 142.829C124.232 143.553 125.768 143.553 127.012 142.829L220.439 88.4572C223.97 86.4024 222.513 81 218.428 81H31.5725C27.4874 81 26.0298 86.4024 29.5605 88.4572L122.988 142.829Z" fill="#F7F7F7"/>
|
||||
<path d="M122.847 136.092C123.849 135.43 125.151 135.43 126.153 136.092L223.668 200.497C226.15 202.136 224.989 206 222.014 206H26.9858C24.0109 206 22.8501 202.136 25.3324 200.497L122.847 136.092Z" fill="#EB6E33"/>
|
||||
<path d="M28 4C28 1.79086 29.7909 0 32 0H218C220.209 0 222 1.79086 222 4V86H28V4Z" fill="#F7F7F7"/>
|
||||
<ellipse cx="124.5" cy="217" rx="98.5" ry="18" fill="url(#paint0_radial_493_11093)"/>
|
||||
<path d="M92.1975 52.8625C91.8675 53.0642 91.5375 53.2383 91.2075 53.385C90.8958 53.5133 90.5658 53.605 90.2175 53.66C90.9325 53.77 91.5925 54.0267 92.1975 54.43L96.57 56.9875L94.975 59.7375L90.6025 57.18C90.2725 56.9783 89.9608 56.7675 89.6675 56.5475C89.3925 56.3275 89.1542 56.0892 88.9525 55.8325C89.1908 56.4375 89.31 57.1342 89.31 57.9225V62.9825H86.065V57.95C86.065 57.5833 86.0833 57.2258 86.12 56.8775C86.175 56.5292 86.2575 56.1992 86.3675 55.8875C86.1658 56.1442 85.9275 56.3733 85.6525 56.575C85.3775 56.7767 85.0842 56.9783 84.7725 57.18L80.4275 59.71L78.805 56.96L83.1775 54.43C83.8742 54.0267 84.5342 53.77 85.1575 53.66C84.5158 53.55 83.8558 53.2842 83.1775 52.8625L78.805 50.305L80.4 47.555L84.7725 50.14C85.1025 50.3417 85.405 50.5525 85.68 50.7725C85.955 50.9742 86.2025 51.2033 86.4225 51.46C86.1842 50.855 86.065 50.1675 86.065 49.3975V44.31H89.31V49.3425C89.31 49.7275 89.2825 50.0942 89.2275 50.4425C89.1908 50.7908 89.1083 51.1208 88.98 51.4325C89.2 51.1758 89.4475 50.9467 89.7225 50.745C89.9975 50.5433 90.2908 50.3417 90.6025 50.14L94.9475 47.61L96.57 50.36L92.1975 52.8625Z" fill="#8F9296"/>
|
||||
<path d="M128.198 53.8625C127.868 54.0642 127.538 54.2383 127.208 54.385C126.896 54.5133 126.566 54.605 126.218 54.66C126.933 54.77 127.593 55.0267 128.198 55.43L132.57 57.9875L130.975 60.7375L126.603 58.18C126.273 57.9783 125.961 57.7675 125.668 57.5475C125.393 57.3275 125.154 57.0892 124.953 56.8325C125.191 57.4375 125.31 58.1342 125.31 58.9225V63.9825H122.065V58.95C122.065 58.5833 122.083 58.2258 122.12 57.8775C122.175 57.5292 122.258 57.1992 122.368 56.8875C122.166 57.1442 121.928 57.3733 121.653 57.575C121.378 57.7767 121.084 57.9783 120.773 58.18L116.428 60.71L114.805 57.96L119.178 55.43C119.874 55.0267 120.534 54.77 121.158 54.66C120.516 54.55 119.856 54.2842 119.178 53.8625L114.805 51.305L116.4 48.555L120.773 51.14C121.103 51.3417 121.405 51.5525 121.68 51.7725C121.955 51.9742 122.203 52.2033 122.423 52.46C122.184 51.855 122.065 51.1675 122.065 50.3975V45.31H125.31V50.3425C125.31 50.7275 125.283 51.0942 125.228 51.4425C125.191 51.7908 125.108 52.1208 124.98 52.4325C125.2 52.1758 125.448 51.9467 125.723 51.745C125.998 51.5433 126.291 51.3417 126.603 51.14L130.948 48.61L132.57 51.36L128.198 53.8625Z" fill="#8F9296"/>
|
||||
<path d="M164.198 53.8625C163.868 54.0642 163.538 54.2383 163.208 54.385C162.896 54.5133 162.566 54.605 162.218 54.66C162.933 54.77 163.593 55.0267 164.198 55.43L168.57 57.9875L166.975 60.7375L162.603 58.18C162.273 57.9783 161.961 57.7675 161.668 57.5475C161.393 57.3275 161.154 57.0892 160.953 56.8325C161.191 57.4375 161.31 58.1342 161.31 58.9225V63.9825H158.065V58.95C158.065 58.5833 158.083 58.2258 158.12 57.8775C158.175 57.5292 158.258 57.1992 158.368 56.8875C158.166 57.1442 157.928 57.3733 157.653 57.575C157.378 57.7767 157.084 57.9783 156.773 58.18L152.428 60.71L150.805 57.96L155.178 55.43C155.874 55.0267 156.534 54.77 157.158 54.66C156.516 54.55 155.856 54.2842 155.178 53.8625L150.805 51.305L152.4 48.555L156.773 51.14C157.103 51.3417 157.405 51.5525 157.68 51.7725C157.955 51.9742 158.203 52.2033 158.423 52.46C158.184 51.855 158.065 51.1675 158.065 50.3975V45.31H161.31V50.3425C161.31 50.7275 161.283 51.0942 161.228 51.4425C161.191 51.7908 161.108 52.1208 160.98 52.4325C161.2 52.1758 161.448 51.9467 161.723 51.745C161.998 51.5433 162.291 51.3417 162.603 51.14L166.948 48.61L168.57 51.36L164.198 53.8625Z" fill="#8F9296"/>
|
||||
<defs>
|
||||
<radialGradient id="paint0_radial_493_11093" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(124.5 217) rotate(90) scale(18 98.5)">
|
||||
<stop stop-color="#8F9296"/>
|
||||
<stop offset="1" stop-color="#D9D9D9" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 4.4 KiB |
@ -0,0 +1,14 @@
|
||||
<svg width="188" height="177" viewBox="0 0 188 177" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="16.5439" y="60.9121" width="154.16" height="94" rx="7" fill="#FF712E"/>
|
||||
<path d="M91.988 107.117C93.2317 107.84 94.7683 107.84 96.012 107.117L162.593 68.3688C166.123 66.314 164.666 60.9116 160.581 60.9116H27.4192C23.3342 60.9116 21.8766 66.314 25.4073 68.3688L91.988 107.117Z" fill="#F5F5F5"/>
|
||||
<path d="M91.9706 102.612C92.9733 101.95 94.2745 101.95 95.2773 102.612L166.131 149.409C168.614 151.048 167.453 154.912 164.478 154.912H22.7697C19.7948 154.912 18.634 151.048 21.1164 149.409L91.9706 102.612Z" fill="#EB6E33"/>
|
||||
<path d="M21.0562 4C21.0562 1.79086 22.847 0 25.0562 0H162.944C165.153 0 166.944 1.79086 166.944 4V64.672H21.0562V4Z" fill="#F5F5F5"/>
|
||||
<ellipse cx="93.624" cy="163.184" rx="74.072" ry="13.536" fill="url(#paint0_radial_492_9893)"/>
|
||||
<path d="M93.4628 24.1714C88.3634 24.1714 83.3786 25.6835 79.1386 28.5166C74.8986 31.3496 71.594 35.3764 69.6425 40.0876C67.6911 44.7988 67.1805 49.9828 68.1754 54.9842C69.1702 59.9856 71.6258 64.5797 75.2316 68.1855C78.8374 71.7913 83.4314 74.2468 88.4328 75.2417C93.4342 76.2365 98.6183 75.7259 103.329 73.7745C108.041 71.823 112.067 68.5184 114.9 64.2784C117.734 60.0385 119.246 55.0536 119.246 49.9542C119.238 43.1184 116.52 36.5646 111.686 31.731C106.852 26.8973 100.299 24.1786 93.4628 24.1714ZM93.4628 71.7705C89.1479 71.7705 84.93 70.491 81.3423 68.0938C77.7547 65.6966 74.9584 62.2894 73.3072 58.303C71.656 54.3166 71.2239 49.93 72.0657 45.6981C72.9075 41.4662 74.9853 37.5789 78.0364 34.5278C81.0874 31.4768 84.9747 29.399 89.2067 28.5572C93.4386 27.7154 97.8251 28.1474 101.812 29.7986C105.798 31.4499 109.205 34.2461 111.602 37.8338C114 41.4214 115.279 45.6394 115.279 49.9542C115.272 55.7383 112.972 61.2835 108.882 65.3734C104.792 69.4633 99.2468 71.7639 93.4628 71.7705ZM91.4795 51.9375V38.0545C91.4795 37.5285 91.6885 37.024 92.0604 36.6521C92.4323 36.2801 92.9368 36.0712 93.4628 36.0712C93.9888 36.0712 94.4933 36.2801 94.8652 36.6521C95.2371 37.024 95.4461 37.5285 95.4461 38.0545V51.9375C95.4461 52.4635 95.2371 52.968 94.8652 53.3399C94.4933 53.7119 93.9888 53.9208 93.4628 53.9208C92.9368 53.9208 92.4323 53.7119 92.0604 53.3399C91.6885 52.968 91.4795 52.4635 91.4795 51.9375ZM96.4377 60.8624C96.4377 61.4508 96.2633 62.0259 95.9364 62.5152C95.6095 63.0044 95.1449 63.3857 94.6013 63.6109C94.0577 63.836 93.4595 63.8949 92.8824 63.7802C92.3053 63.6654 91.7752 63.382 91.3592 62.966C90.9431 62.5499 90.6598 62.0198 90.545 61.4427C90.4302 60.8657 90.4891 60.2675 90.7143 59.7239C90.9395 59.1803 91.3208 58.7157 91.81 58.3888C92.2992 58.0619 92.8744 57.8874 93.4628 57.8874C94.2518 57.8874 95.0085 58.2009 95.5664 58.7588C96.1243 59.3167 96.4377 60.0734 96.4377 60.8624Z" fill="#FF5200"/>
|
||||
<defs>
|
||||
<radialGradient id="paint0_radial_492_9893" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(93.624 163.184) rotate(90) scale(13.536 74.072)">
|
||||
<stop stop-color="#8F9296"/>
|
||||
<stop offset="1" stop-color="#D9D9D9" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 3.0 KiB |
@ -0,0 +1,31 @@
|
||||
<svg width="301" height="385" viewBox="0 0 301 385" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M154.67 299.283C234.183 299.283 298.642 281.802 298.642 260.238C298.642 238.674 234.183 221.193 154.67 221.193C75.1559 221.193 10.6973 238.674 10.6973 260.238C10.6973 281.802 75.1559 299.283 154.67 299.283Z" fill="url(#paint0_radial_493_11687)"/>
|
||||
<path d="M50.0446 136.566C50.4073 135.113 50.0446 133.66 49.138 132.571L28.4669 108.236C26.1097 105.33 21.3952 106.601 20.4886 110.234C20.1259 111.686 20.4886 113.139 21.3952 114.229L42.0663 138.382C44.4235 141.288 49.138 140.198 50.0446 136.566Z" fill="#D9D9D9"/>
|
||||
<path d="M206.528 114.047C208.522 114.592 210.517 113.866 211.786 112.231L246.238 67.3749C248.414 64.6509 246.963 60.6556 243.699 59.7476C241.705 59.2028 239.71 59.9292 238.441 61.5636L203.989 106.42C201.632 109.325 203.083 113.321 206.528 114.047Z" fill="#D9D9D9"/>
|
||||
<path d="M100.272 111.505C101.179 109.87 101.179 108.054 100.091 106.601L67.8149 62.2901C65.8203 59.566 61.6498 59.7476 59.8366 62.6533C58.93 64.2877 58.93 66.1038 60.0179 67.5566L92.2938 111.686C94.2884 114.41 98.4589 114.229 100.272 111.505Z" fill="#D9D9D9"/>
|
||||
<path d="M247.145 147.099C248.595 147.281 250.046 146.736 250.953 145.465L271.624 120.04C273.8 117.498 272.168 113.502 268.904 113.139C267.453 112.958 266.003 113.502 265.096 114.774L244.425 140.198C242.249 142.741 243.7 146.736 247.145 147.099Z" fill="#D9D9D9"/>
|
||||
<mask id="mask0_493_11687" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="100" y="0" width="103" height="102">
|
||||
<path d="M202.902 0H100.453V101.153H202.902V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_493_11687)">
|
||||
<path d="M173.527 47.3985C173.889 47.7617 174.071 48.125 174.252 48.4882C174.433 48.8514 174.433 49.2146 174.433 49.7594C174.433 50.1226 174.433 50.6674 174.252 51.0306C174.071 51.3938 173.889 51.757 173.527 52.1202L151.224 73.9127C150.861 74.2759 150.498 74.4575 150.136 74.6391C149.773 74.8207 149.41 74.8207 148.866 74.8207C148.504 74.8207 147.96 74.8207 147.597 74.6391C147.234 74.4575 146.872 74.2759 146.509 73.9127L136.899 64.4693C136.355 63.9245 135.992 63.0165 135.992 62.29C135.992 61.382 136.355 60.6556 136.899 60.1108C137.443 59.566 138.349 59.2028 139.075 59.2028C139.981 59.2028 140.707 59.566 141.251 60.1108L148.504 67.3749L168.631 47.3985C168.993 47.0353 169.356 46.8537 169.719 46.6721C170.081 46.4905 170.444 46.4905 170.988 46.4905C171.351 46.4905 171.895 46.4905 172.257 46.6721C172.983 46.8537 173.345 47.0353 173.527 47.3985ZM196.918 59.0212C196.918 67.1933 194.56 75.1839 189.846 81.9033C185.313 88.6226 178.785 93.8891 171.169 96.9764C163.554 100.064 155.213 100.972 147.053 99.3372C139.075 97.7028 131.64 93.8891 125.838 88.0778C120.036 82.2665 116.046 75.0023 114.415 67.0117C112.783 59.0212 113.689 50.849 116.772 43.2216C119.854 35.5943 125.294 29.2382 132.003 24.6981C138.712 20.158 146.872 17.7971 155.031 17.7971C166.092 17.7971 176.609 22.1556 184.406 29.783C192.566 37.7735 196.918 48.125 196.918 59.0212ZM190.571 59.0212C190.571 52.1202 188.577 45.4009 184.587 39.7712C180.78 33.9599 175.159 29.6014 168.812 26.8773C162.466 24.1533 155.394 23.6084 148.504 24.8797C141.613 26.1509 135.448 29.6014 130.552 34.3231C125.657 39.2264 122.212 45.4009 120.942 52.1202C119.673 58.8396 120.217 65.9221 122.937 72.2783C125.657 78.6344 130.19 84.0825 135.811 87.8962C141.613 91.7099 148.322 93.7075 155.394 93.7075C164.642 93.7075 173.708 90.0754 180.236 83.5377C186.763 77.1816 190.571 68.283 190.571 59.0212Z" fill="#FF5200"/>
|
||||
</g>
|
||||
<path d="M86.1302 159.993C113.692 177.245 115.686 197.04 81.5971 206.12L28.4688 175.429L86.1302 159.993Z" fill="#54565A"/>
|
||||
<path d="M156.846 249.705L268.724 219.741L258.932 214.111C217.228 189.958 140.708 187.597 71.8047 200.672L156.846 249.705Z" fill="#54565A"/>
|
||||
<path d="M85.7675 160.175C107.527 173.613 109.702 193.408 76.3386 203.215L28.4688 175.429L85.7675 160.175Z" fill="#FF4D00"/>
|
||||
<path d="M156.846 249.705L268.905 219.559L264.554 217.017C222.849 192.863 139.983 189.413 76.3379 203.215L156.846 249.705Z" fill="#FF4D00"/>
|
||||
<path d="M120.4 167.802L101.361 156.906L108.252 155.634C109.34 155.453 110.79 155.634 111.516 155.998L130.555 167.076L123.664 168.347C122.395 168.347 121.126 168.347 120.4 167.802Z" fill="#FF4D00"/>
|
||||
<path d="M158.661 157.087L141.253 147.099L134.182 148.915L151.589 159.085L158.661 157.087Z" fill="#FF4D00"/>
|
||||
<path d="M161.018 168.528L129.286 150.005L122.939 151.821L154.853 170.163L161.018 168.528Z" fill="#54565A"/>
|
||||
<path d="M164.281 178.698L119.493 152.91L113.328 154.545L158.115 180.514L164.281 178.698Z" fill="#54565A"/>
|
||||
<path d="M156.847 249.705L28.4688 175.429V209.934L156.847 288.568V249.705Z" fill="white"/>
|
||||
<path d="M87.581 223.917L60.0195 208.118V217.38L87.581 233.179V223.917Z" fill="#FF4D00"/>
|
||||
<path d="M126.566 249.705L89.2129 228.094V234.451L126.566 256.061V249.705Z" fill="#54565A"/>
|
||||
<path d="M268.907 219.559V254.064L156.848 288.569V249.705L268.907 219.559Z" fill="#939598"/>
|
||||
<defs>
|
||||
<radialGradient id="paint0_radial_493_11687" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(154.664 260.216) rotate(90) scale(39.0156 143.925)">
|
||||
<stop stop-color="#8F9296"/>
|
||||
<stop offset="1" stop-color="#D9D9D9" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 5.1 KiB |
1
src/pages/activation/activate/pages/creation/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './Creation'
|
99
src/pages/activation/activate/pages/creation/styled.tsx
Normal file
@ -0,0 +1,99 @@
|
||||
import styled from 'styled-components'
|
||||
import { Paragraph } from 'components/Typography'
|
||||
import { Steps } from 'components/Steps'
|
||||
import { Form } from 'antd'
|
||||
import { Button } from 'components/Button'
|
||||
|
||||
export const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 25px 40px 58px 40px;
|
||||
align-items: center;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
padding: 15px 0 58px 0;
|
||||
}
|
||||
`
|
||||
|
||||
export const StyledMain = styled.div`
|
||||
width: 100%;
|
||||
max-width: 512px;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
max-width: 100%;
|
||||
}
|
||||
`
|
||||
|
||||
export const StyledStepItemContainer = styled.div`
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
`
|
||||
|
||||
export const StyledSteps = styled(Steps)`
|
||||
margin-bottom: 13px;
|
||||
|
||||
${props => props.theme.breakpoints.up('s')} {
|
||||
margin-left: -37px;
|
||||
width: calc(100% + 37px);
|
||||
margin-bottom: 75px;
|
||||
}
|
||||
`
|
||||
export const StyledBar = styled.div`
|
||||
height: 5px;
|
||||
margin-bottom: 22px;
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
height: 5px;
|
||||
width: 134px;
|
||||
background: #000022;
|
||||
border-radius: 5px;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translate3d(-50%, 0, 0);
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
|
||||
export const StyledParagraph = styled(Paragraph)`
|
||||
margin-bottom: 40px !important;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
margin-bottom: 6px !important;
|
||||
}
|
||||
`
|
||||
|
||||
export const StyledAccountWrap = styled.div`
|
||||
padding-top: 10px
|
||||
`
|
||||
|
||||
export const StyledAccountForm = styled(Form)`
|
||||
margin-top: 37px;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
margin-top: 32px;
|
||||
}
|
||||
|
||||
.ant-form-item-required {
|
||||
width: 100%;
|
||||
}
|
||||
`
|
||||
export const StyledHelp = styled.div`
|
||||
padding: 0;
|
||||
color: #1E1D1F;
|
||||
font-size: 10px;
|
||||
line-height: 14px;
|
||||
`
|
||||
|
||||
export const StyledSubmitButton = styled(Button)`
|
||||
width: 358px;
|
||||
|
||||
${props => props.theme.breakpoints.down('s')} {
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
`
|
@ -1,7 +1,7 @@
|
||||
import React, { FC } from 'react'
|
||||
import { StyledContainer, StyledImageWrapper, StyledMainContent, StyledTitle, StyledContent } from './styled'
|
||||
import { Divider } from 'antd'
|
||||
import { Button } from '../../../components/Button'
|
||||
import { Button } from 'components/Button'
|
||||
|
||||
export const Home: FC = () => {
|
||||
return (
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import Image from './images/homePic@2x.png'
|
||||
import { Title } from '../../../components/Typography'
|
||||
import { Title } from 'components/Typography'
|
||||
|
||||
export const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
|
@ -1,13 +1,12 @@
|
||||
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 { 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 { Button } from 'components/Button'
|
||||
import { Input, Password, ErrorMessage, LabelWithTooltip, Captcha } from 'components/FormControl'
|
||||
import Image from './images/password@2x.png'
|
||||
|
||||
// TODO: Captcha
|
||||
type Props = Pick<ModalProps, 'open'> & {
|
||||
onClose?: () => void
|
||||
}
|
||||
@ -19,7 +18,7 @@ const StyledWrapper = styled.div`
|
||||
min-height: 366px;
|
||||
|
||||
${props => props.theme.breakpoints.up('s')} {
|
||||
justify-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
`
|
||||
const StyledMain = styled.div`
|
||||
@ -132,10 +131,14 @@ export const ForgetPasswordModal: FC<Props> = ({ onClose, ...props }) => {
|
||||
rules={[
|
||||
{
|
||||
required: true, message: <ErrorMessage message="Please input your code" />
|
||||
}, {
|
||||
len: 6,
|
||||
validateTrigger: 'onSubmit',
|
||||
message: <ErrorMessage message="The verification code you entered is invalid." />
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
<Captcha />
|
||||
</Form.Item>
|
||||
<StyledCaptchaHint>
|
||||
The code will expire in {expiration}<br />
|
||||
@ -198,7 +201,7 @@ export const ForgetPasswordModal: FC<Props> = ({ onClose, ...props }) => {
|
||||
<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' }} />
|
||||
<img src={Image} style={{ width: 232, height: 83, margin: 'auto', display: 'block' }} alt="" />
|
||||
<Button block style={{ marginTop: 20 }} onClick={handleLogin}>Log in</Button>
|
||||
</>
|
||||
) : null}
|
||||
|
@ -9,8 +9,8 @@ import {
|
||||
StyledTitle, StyledHint, StyledHelp
|
||||
} from './styled'
|
||||
import { Form } from 'antd'
|
||||
import { Paragraph, Link } from '../../../components/Typography'
|
||||
import { Input, Password, ErrorMessage } from '../../../components/FormControl'
|
||||
import { Paragraph, Link } from 'components/Typography'
|
||||
import { Input, Password, ErrorMessage } from 'components/FormControl'
|
||||
import { ForgetPasswordModal } from './ForgetPasswordModal'
|
||||
|
||||
export const Login: FC = () => {
|
||||
|
@ -1,8 +1,7 @@
|
||||
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'
|
||||
import { Title } from 'components/Typography'
|
||||
import { Button } from 'components/Button'
|
||||
|
||||
export const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
|
64
src/stores/ActivationStore.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { makeAutoObservable } from 'mobx'
|
||||
|
||||
export class ActivationStore {
|
||||
static singleton: ActivationStore | null = null
|
||||
|
||||
static instance() {
|
||||
if (!ActivationStore.singleton) {
|
||||
ActivationStore.singleton = new ActivationStore()
|
||||
}
|
||||
return ActivationStore.singleton
|
||||
}
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this)
|
||||
}
|
||||
|
||||
shouldCreateAccount = true
|
||||
|
||||
setShouldCreateAccount(shouldCreateAccount: boolean) {
|
||||
this.shouldCreateAccount = shouldCreateAccount
|
||||
}
|
||||
|
||||
code: string = '';
|
||||
|
||||
setCode(code: string) {
|
||||
this.code = code
|
||||
}
|
||||
|
||||
step: number = 0
|
||||
|
||||
get stepItems(): Array<{ title: string }> {
|
||||
return (this.shouldCreateAccount ? [{
|
||||
title: 'Account',
|
||||
}] : []).concat([{
|
||||
title: 'Tester Info',
|
||||
},
|
||||
{
|
||||
title: 'Review'
|
||||
},
|
||||
{
|
||||
title: 'Done'
|
||||
}])
|
||||
}
|
||||
|
||||
handlePrev() {
|
||||
if (this.step === 0) return
|
||||
this.step = this.step - 1
|
||||
console.log('step', this.step)
|
||||
}
|
||||
|
||||
handleNext() {
|
||||
if (this.step === this.stepItems.length - 1) return
|
||||
this.step = this.step + 1
|
||||
console.log('step', this.step)
|
||||
}
|
||||
|
||||
testerInfo: any = {}
|
||||
|
||||
setTesterInfo(testerInfo: any) {
|
||||
this.testerInfo = testerInfo
|
||||
|
||||
}
|
||||
|
||||
}
|
52
src/stores/AuthStore.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { makeAutoObservable } from 'mobx'
|
||||
import storage from '../utils/storage'
|
||||
import { Token, UserInfo } from '../constants/storage'
|
||||
|
||||
export interface IUserInfo {
|
||||
firstName: string
|
||||
lastName: string
|
||||
gender: number
|
||||
email: string
|
||||
}
|
||||
|
||||
export class AuthStore {
|
||||
static singleton: AuthStore | null = null
|
||||
|
||||
static instance() {
|
||||
if (!AuthStore.singleton) {
|
||||
AuthStore.singleton = new AuthStore()
|
||||
}
|
||||
return AuthStore.singleton
|
||||
}
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this)
|
||||
}
|
||||
|
||||
get token(): string | null {
|
||||
return storage.get<string>(Token)
|
||||
}
|
||||
|
||||
get userInfo(): IUserInfo | null {
|
||||
return storage.get(UserInfo)
|
||||
}
|
||||
|
||||
get userName(): string {
|
||||
if (!this.userInfo) {
|
||||
return ''
|
||||
}
|
||||
return [this.userInfo.firstName, this.userInfo.lastName].join(' ')
|
||||
}
|
||||
|
||||
get logined() {
|
||||
return !!this.token
|
||||
}
|
||||
|
||||
register() {}
|
||||
|
||||
login() {}
|
||||
|
||||
getUserInfo() {}
|
||||
|
||||
resetPassword() {}
|
||||
}
|
@ -15,6 +15,7 @@ html, body {
|
||||
line-height: 50px !important;
|
||||
}
|
||||
|
||||
|
||||
.ant-btn-link, .ant-btn-text {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
@ -72,6 +73,10 @@ html, body {
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.ant-input:read-only {
|
||||
color: #CACACC;
|
||||
}
|
||||
|
||||
.ant-input-status-error:not(.ant-input-disabled):not(.ant-input-borderless).ant-input,
|
||||
.ant-input-affix-wrapper-status-error:not(.ant-input-affix-wrapper-disabled):not(.ant-input-affix-wrapper-borderless).ant-input-affix-wrapper {
|
||||
outline: 1px solid #FF5200;
|
||||
@ -126,7 +131,7 @@ html, body {
|
||||
}
|
||||
|
||||
.ant-radio-wrapper .ant-radio-checked .ant-radio-inner::after {
|
||||
background: #FF5200;
|
||||
background: #000022;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
@ -173,6 +178,10 @@ html, body {
|
||||
border-bottom: 2px solid #fff;
|
||||
}
|
||||
|
||||
.ant-select-dropdown .ant-select-item-option-selected:not(.ant-select-item-option-disabled) {
|
||||
background: #F3F3F3;
|
||||
}
|
||||
|
||||
.ant-form-item .ant-form-item-label > label.ant-form-item-required:not(.ant-form-item-required-mark-optional)::before {
|
||||
display: none;
|
||||
}
|
||||
@ -195,14 +204,6 @@ html, body {
|
||||
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;
|
||||
@ -218,3 +219,22 @@ html, body {
|
||||
.ant-form-item .ant-form-item-label > label .ant-form-item-tooltip {
|
||||
color: #000022;
|
||||
}
|
||||
|
||||
h1.ant-typography, div.ant-typography-h1, div.ant-typography-h1 > textarea, .ant-typography h1,
|
||||
h2.ant-typography, div.ant-typography-h2, div.ant-typography-h2 > textarea, .ant-typography h2,
|
||||
div.ant-typography, .ant-typography p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.ant-typography + h1.ant-typography, .ant-typography + h2.ant-typography, .ant-typography + h3.ant-typography, .ant-typography + h4.ant-typography, .ant-typography + h5.ant-typography {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.ant-steps.ant-steps-label-vertical .ant-steps-item-content {
|
||||
width: auto;
|
||||
max-width: 112px;
|
||||
}
|
||||
|
||||
.ant-steps .ant-steps-item:last-child {
|
||||
flex: 1;
|
||||
}
|
||||
|
33
src/utils/storage.ts
Normal file
@ -0,0 +1,33 @@
|
||||
const NameSpace = 'ihealth-kit-registration_';
|
||||
|
||||
function getData<T>(key: string, defaultValue?: T): T | null {
|
||||
const rawData = window.localStorage.getItem(NameSpace + key);
|
||||
try {
|
||||
if (rawData) {
|
||||
return JSON.parse(rawData);
|
||||
} else {
|
||||
return defaultValue || null
|
||||
}
|
||||
} catch (e) {
|
||||
return defaultValue || null;
|
||||
}
|
||||
}
|
||||
|
||||
function setData<T>(key: string, data: T): void {
|
||||
window.localStorage.setItem(NameSpace + key, JSON.stringify(data));
|
||||
}
|
||||
|
||||
function clearData(key?: string): void {
|
||||
if (key) {
|
||||
window.localStorage.removeItem(NameSpace + key);
|
||||
} else {
|
||||
window.localStorage.clear()
|
||||
}
|
||||
}
|
||||
|
||||
const storage = {
|
||||
get: getData,
|
||||
set: setData,
|
||||
clear: clearData
|
||||
}
|
||||
export default storage
|
@ -18,7 +18,8 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": "src"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
|
@ -9561,6 +9561,11 @@ typeface-lato@^1.1.13:
|
||||
resolved "https://registry.npmmirror.com/typeface-lato/-/typeface-lato-1.1.13.tgz#630ec75394d7fd2c63148005e6e23f2d8014e931"
|
||||
integrity sha512-sTn5k3+fagGOi8FQfN2MPeUiTdvG0Z3LVqCaQdsg2sYa0fzNteFZussizdwiPxF45OoFN3zdU/ci+PtjolNSPQ==
|
||||
|
||||
typeface-roboto@^1.1.13:
|
||||
version "1.1.13"
|
||||
resolved "https://registry.npmmirror.com/typeface-roboto/-/typeface-roboto-1.1.13.tgz#9c4517cb91e311706c74823e857b4bac9a764ae5"
|
||||
integrity sha512-YXvbd3a1QTREoD+FJoEkl0VQNJoEjewR2H11IjVv4bp6ahuIcw0yyw/3udC4vJkHw3T3cUh85FTg8eWef3pSaw==
|
||||
|
||||
typescript@^4.9.5:
|
||||
version "4.9.5"
|
||||
resolved "https://registry.npmmirror.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
|
||||
|