diff --git a/package.json b/package.json index e817499..d0c259c 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/src/components/FormControl/Captcha.tsx b/src/components/FormControl/Captcha.tsx new file mode 100644 index 0000000..3c8254b --- /dev/null +++ b/src/components/FormControl/Captcha.tsx @@ -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 = ({ value, onChange, length = 6 }) => { + const inputDomRefs: RefObject[] = Array.from({ length }, () => React.createRef()) + 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) => { + textSelect(e.target) + } + const handleClick = (e: MouseEvent) => { + textSelect(e.target as HTMLInputElement) + } + const handleChange = (index: number) => (e: ChangeEvent) => { + 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) => { + 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 ( + + { + Array.from({ length }, () => "").map((_, index) => { + return ( + + + + ) + }) + } + + ) +} diff --git a/src/components/FormControl/ErrorMessage.tsx b/src/components/FormControl/ErrorMessage.tsx index 780e1fb..330902c 100644 --- a/src/components/FormControl/ErrorMessage.tsx +++ b/src/components/FormControl/ErrorMessage.tsx @@ -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> = ({ message, children }) => { diff --git a/src/components/FormControl/LabelWithTooltip.tsx b/src/components/FormControl/LabelWithTooltip.tsx index 6e25f0c..6a2c0b3 100644 --- a/src/components/FormControl/LabelWithTooltip.tsx +++ b/src/components/FormControl/LabelWithTooltip.tsx @@ -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 void }>> = ({ onClick, children }) => { + const handleClick = (e: any) => { + e.preventDefault() + onClick && onClick(e) + } + return {children} +} export const LabelWithTooltip: FC> = ({ primary, children, @@ -24,7 +32,9 @@ export const LabelWithTooltip: FC {children} - + + + ) diff --git a/src/components/FormControl/index.ts b/src/components/FormControl/index.ts index 13b043a..17345dd 100644 --- a/src/components/FormControl/index.ts +++ b/src/components/FormControl/index.ts @@ -7,3 +7,4 @@ export * from './Radio' export * from './RadioGroup' export * from './ErrorMessage' export * from './LabelWithTooltip' +export * from './Captcha' diff --git a/src/components/Steps/Steps.tsx b/src/components/Steps/Steps.tsx index cd30938..f243fa1 100644 --- a/src/components/Steps/Steps.tsx +++ b/src/components/Steps/Steps.tsx @@ -38,6 +38,6 @@ export const Steps: FC = ({ items, current = 0, ...props }) => { } }) return ( - + ) } diff --git a/src/components/Tag/Tag.tsx b/src/components/Tag/Tag.tsx index a03f517..dbbfaae 100644 --- a/src/components/Tag/Tag.tsx +++ b/src/components/Tag/Tag.tsx @@ -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 = (props) => { diff --git a/src/components/Typography/Link.tsx b/src/components/Typography/Link.tsx new file mode 100644 index 0000000..bc1fb83 --- /dev/null +++ b/src/components/Typography/Link.tsx @@ -0,0 +1,16 @@ +import React, { FC, ComponentProps } from 'react' +import { Typography, TypographyProps } from 'antd' +import styled from 'styled-components' + +type LinkProps = ComponentProps & { + inherit?: boolean +} + +const StyledLink = styled(Typography.Link)<{ + $inherit?: boolean +}>` + ${({ $inherit }) => $inherit ? 'font-size: inherit;line-height:inherit;' : ''} +` +export const Link: FC = ({ inherit, ...props }) => { + return +} diff --git a/src/components/Typography/TinyText.tsx b/src/components/Typography/TinyText.tsx index 8678d9d..63253aa 100644 --- a/src/components/Typography/TinyText.tsx +++ b/src/components/Typography/TinyText.tsx @@ -7,7 +7,6 @@ type TinyTextProps = ComponentProps const StyledTinyText = styled(Typography.Text)` font-weight: 400; font-size: 12px; - transform: scale(0.66667); line-height: 1.75; color: #1E1D1F; ` diff --git a/src/components/Typography/index.ts b/src/components/Typography/index.ts index 4e325f1..fa4df4e 100644 --- a/src/components/Typography/index.ts +++ b/src/components/Typography/index.ts @@ -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 } diff --git a/src/constants/storage.ts b/src/constants/storage.ts new file mode 100644 index 0000000..3b5e254 --- /dev/null +++ b/src/constants/storage.ts @@ -0,0 +1,2 @@ +export const Token = 'token' +export const UserInfo = 'user-info' diff --git a/src/icons/caretLeft.svg b/src/icons/caretLeft.svg index c6171fb..98ad185 100644 --- a/src/icons/caretLeft.svg +++ b/src/icons/caretLeft.svg @@ -1,3 +1,3 @@ - + diff --git a/src/index.tsx b/src/index.tsx index d9e296d..56d4bcd 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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'; diff --git a/src/pages/activation/activate/Activate.tsx b/src/pages/activation/activate/Activate.tsx index 474d702..0b5f74a 100644 --- a/src/pages/activation/activate/Activate.tsx +++ b/src/pages/activation/activate/Activate.tsx @@ -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 ( - - - - - {logined ? ( - - Hello, John -
- Welcome to your kit activation. -
- ) : ( - - Welcome to your kit activation. - - )} - Your kit needs to be activated before the lab can process your sample. - -
- - }, - // Activation code not found - ]} - > - - - - Next - -
- - - {!logined ? - <> - Already have an account?  - Log in -
- : null} - Can’t find your activation code? -
-
-
-
-
- -
+ <> + {!code ? : } + ) -} +}) diff --git a/src/pages/activation/activate/ActivateGuide.tsx b/src/pages/activation/activate/pages/activate/ActivateGuide.tsx similarity index 90% rename from src/pages/activation/activate/ActivateGuide.tsx rename to src/pages/activation/activate/pages/activate/ActivateGuide.tsx index 9212d66..f31f4be 100644 --- a/src/pages/activation/activate/ActivateGuide.tsx +++ b/src/pages/activation/activate/pages/activate/ActivateGuide.tsx @@ -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 & { onClose?: () => void diff --git a/src/pages/activation/activate/pages/activate/ActivateStarter.tsx b/src/pages/activation/activate/pages/activate/ActivateStarter.tsx new file mode 100644 index 0000000..147e4b3 --- /dev/null +++ b/src/pages/activation/activate/pages/activate/ActivateStarter.tsx @@ -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 ( + + + + + {logined ? ( + + Hello, John +
+ Welcome to your kit activation. +
+ ) : ( + + Welcome to your kit activation. + + )} + Your kit needs to be activated before the lab can process your sample. + +
+ + }, + // Activation code not found + ]} + > + + + + Next + +
+ + + {!logined ? + <> + Already have an account?  + Log in +
+ : null} + Can’t find your activation code? +
+
+
+
+
+ +
+ ) +}) diff --git a/src/pages/activation/activate/images/activate@2x.png b/src/pages/activation/activate/pages/activate/images/activate@2x.png similarity index 100% rename from src/pages/activation/activate/images/activate@2x.png rename to src/pages/activation/activate/pages/activate/images/activate@2x.png diff --git a/src/pages/activation/activate/images/activateMobile@2x.png b/src/pages/activation/activate/pages/activate/images/activateMobile@2x.png similarity index 100% rename from src/pages/activation/activate/images/activateMobile@2x.png rename to src/pages/activation/activate/pages/activate/images/activateMobile@2x.png diff --git a/src/pages/activation/activate/pages/activate/index.ts b/src/pages/activation/activate/pages/activate/index.ts new file mode 100644 index 0000000..a507bd4 --- /dev/null +++ b/src/pages/activation/activate/pages/activate/index.ts @@ -0,0 +1 @@ +export * from './ActivateStarter' diff --git a/src/pages/activation/activate/styled.tsx b/src/pages/activation/activate/pages/activate/styled.tsx similarity index 92% rename from src/pages/activation/activate/styled.tsx rename to src/pages/activation/activate/pages/activate/styled.tsx index 793c371..8d8a07b 100644 --- a/src/pages/activation/activate/styled.tsx +++ b/src/pages/activation/activate/pages/activate/styled.tsx @@ -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; diff --git a/src/pages/activation/activate/pages/creation/Account.tsx b/src/pages/activation/activate/pages/creation/Account.tsx new file mode 100644 index 0000000..87296ec --- /dev/null +++ b/src/pages/activation/activate/pages/creation/Account.tsx @@ -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 ( + + Create an account to activate your kit + If you already have an account, log + in to continue. + + + + + }, + ]} + > + + + + + + }, + ]}> + + + + + + }, + { type: 'email', message: } + ]} + > + + + + }, + { type: 'email', message: }, + ({ 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')); + }, + }), + ]} + > + + + + Please enter a new password + + } + name="password" + rules={[ + { required: true, message: }, + { + pattern: /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$/, + message: + } + ]} + > + + + }, + ({ 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={By clicking "Next" you acknowledge the Privacy Policy and agree to + our Terms of Use.} + > + + + + Next + + + +
+ Disclaimer + + Privacy +
+
+ +
+ ) +}) diff --git a/src/pages/activation/activate/pages/creation/CityCountrySelect.tsx b/src/pages/activation/activate/pages/creation/CityCountrySelect.tsx new file mode 100644 index 0000000..b6f432b --- /dev/null +++ b/src/pages/activation/activate/pages/creation/CityCountrySelect.tsx @@ -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 = ({ style, ...props }) => { + return ( + + + + + + + ) +} + diff --git a/src/pages/activation/activate/pages/creation/Creation.tsx b/src/pages/activation/activate/pages/creation/Creation.tsx new file mode 100644 index 0000000..d23eace --- /dev/null +++ b/src/pages/activation/activate/pages/creation/Creation.tsx @@ -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 ( + + + + + + + + {step !== 3 ? + You are activating your iHealth CheckMeSafe Home Collection Kit Critical 2 (HIV & + Syphilis), + ID {code} : null} + {step === 0 ? : null} + {step === 1 ? : null} + {step === 2 ? : null} + {step === 3 ? : null} + + + + ) +}) diff --git a/src/pages/activation/activate/pages/creation/Done.tsx b/src/pages/activation/activate/pages/creation/Done.tsx new file mode 100644 index 0000000..d9c3a60 --- /dev/null +++ b/src/pages/activation/activate/pages/creation/Done.tsx @@ -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 ( + <> + Your kit is activated! + You have just activated your iHealth CheckmeSafe Home Collection Kit Critical 2 - HIV & Syphilis + ID {code} + + + + What’s Next? +
+ + Watch a video on how to collect your sample + + + Go to my dashboard + + + View FAQs + +
+ + ) +}) diff --git a/src/pages/activation/activate/pages/creation/EthnicitySelect.tsx b/src/pages/activation/activate/pages/creation/EthnicitySelect.tsx new file mode 100644 index 0000000..031c9d7 --- /dev/null +++ b/src/pages/activation/activate/pages/creation/EthnicitySelect.tsx @@ -0,0 +1,24 @@ +import React, { FC } from 'react' +import { Select, SelectProps } from 'components/FormControl' + + +export const EthnicitySelect: FC = (props) => { + return ( + + ) +} diff --git a/src/pages/activation/activate/pages/creation/Review.tsx b/src/pages/activation/activate/pages/creation/Review.tsx new file mode 100644 index 0000000..3e4fd48 --- /dev/null +++ b/src/pages/activation/activate/pages/creation/Review.tsx @@ -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> = ({ label, children }) => { + return ( + + {label} + {children} + + ) +} +export const Review: FC = observer(() => { + const { userName, userInfo } = AuthStore.instance() + const [store] = useState(ActivationStore.instance()) + const { testerInfo } = store + + return ( + <> + Hello, {userName} + Please review the tester’s information below. + + + {testerInfo.firstName} + + + {testerInfo.lastName} + + + {testerInfo.gender === 'female' ? 'Female' : 'Male'} + {testerInfo.race} + {testerInfo.ethnicity} + {testerInfo.phone} + {[testerInfo.address, testerInfo.address2, testerInfo.city + ', ' + testerInfo.ca, testerInfo.postal].filter(Boolean).map((text, index) => ( +
{text}
+ ))}
+ +
{testerInfo.email || userInfo?.email}
+ {(testerInfo.additionalEmails || []).map((e: string, index: number) =>
{e}
)} +
+ + + + + ) : ( + <> + Verify your email + A 6-digit code has been sent to {userInfo?.email} +
+ + }, + { + len: 6, + validateTrigger: 'onSubmit', + message: + }, + ]}> + + + The code will expire in {expried} + Didn’t receive the code? Send + again + + + +
)} + + + + ) +}) diff --git a/src/pages/activation/activate/pages/creation/caData.ts b/src/pages/activation/activate/pages/creation/caData.ts new file mode 100644 index 0000000..d4636d3 --- /dev/null +++ b/src/pages/activation/activate/pages/creation/caData.ts @@ -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" +] diff --git a/src/pages/activation/activate/pages/creation/images/email.svg b/src/pages/activation/activate/pages/creation/images/email.svg new file mode 100644 index 0000000..96dff0e --- /dev/null +++ b/src/pages/activation/activate/pages/creation/images/email.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/pages/activation/activate/pages/creation/images/emailWarning.svg b/src/pages/activation/activate/pages/creation/images/emailWarning.svg new file mode 100644 index 0000000..7a609cc --- /dev/null +++ b/src/pages/activation/activate/pages/creation/images/emailWarning.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/pages/activation/activate/pages/creation/images/success.svg b/src/pages/activation/activate/pages/creation/images/success.svg new file mode 100644 index 0000000..6fc8a22 --- /dev/null +++ b/src/pages/activation/activate/pages/creation/images/success.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/activation/activate/pages/creation/index.ts b/src/pages/activation/activate/pages/creation/index.ts new file mode 100644 index 0000000..9f00fd7 --- /dev/null +++ b/src/pages/activation/activate/pages/creation/index.ts @@ -0,0 +1 @@ +export * from './Creation' diff --git a/src/pages/activation/activate/pages/creation/styled.tsx b/src/pages/activation/activate/pages/creation/styled.tsx new file mode 100644 index 0000000..5496c90 --- /dev/null +++ b/src/pages/activation/activate/pages/creation/styled.tsx @@ -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%; + + } +` diff --git a/src/pages/activation/home/Home.tsx b/src/pages/activation/home/Home.tsx index 889eee2..0cba438 100644 --- a/src/pages/activation/home/Home.tsx +++ b/src/pages/activation/home/Home.tsx @@ -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 ( diff --git a/src/pages/activation/home/styled.tsx b/src/pages/activation/home/styled.tsx index 859af27..62d3f14 100644 --- a/src/pages/activation/home/styled.tsx +++ b/src/pages/activation/home/styled.tsx @@ -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; diff --git a/src/pages/activation/login/ForgetPasswordModal.tsx b/src/pages/activation/login/ForgetPasswordModal.tsx index 8484d48..5716a0a 100644 --- a/src/pages/activation/login/ForgetPasswordModal.tsx +++ b/src/pages/activation/login/ForgetPasswordModal.tsx @@ -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 & { 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 = ({ onClose, ...props }) => { rules={[ { required: true, message: + }, { + len: 6, + validateTrigger: 'onSubmit', + message: }, ]} > - + The code will expire in {expiration}
@@ -198,7 +201,7 @@ export const ForgetPasswordModal: FC = ({ onClose, ...props }) => { Your password has just been reset, please log in to continue. - + ) : null} diff --git a/src/pages/activation/login/Login.tsx b/src/pages/activation/login/Login.tsx index f5f635b..d58a7b1 100644 --- a/src/pages/activation/login/Login.tsx +++ b/src/pages/activation/login/Login.tsx @@ -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 = () => { diff --git a/src/pages/activation/login/styled.tsx b/src/pages/activation/login/styled.tsx index fe62e23..dc584e7 100644 --- a/src/pages/activation/login/styled.tsx +++ b/src/pages/activation/login/styled.tsx @@ -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; diff --git a/src/stores/ActivationStore.ts b/src/stores/ActivationStore.ts new file mode 100644 index 0000000..f4d027a --- /dev/null +++ b/src/stores/ActivationStore.ts @@ -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 + + } + +} diff --git a/src/stores/AuthStore.ts b/src/stores/AuthStore.ts new file mode 100644 index 0000000..3190a81 --- /dev/null +++ b/src/stores/AuthStore.ts @@ -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(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() {} +} diff --git a/src/styles/reset.css b/src/styles/reset.css index 5b0df83..befd084 100644 --- a/src/styles/reset.css +++ b/src/styles/reset.css @@ -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; +} diff --git a/src/utils/storage.ts b/src/utils/storage.ts new file mode 100644 index 0000000..477abf0 --- /dev/null +++ b/src/utils/storage.ts @@ -0,0 +1,33 @@ +const NameSpace = 'ihealth-kit-registration_'; + +function getData(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(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 diff --git a/tsconfig.json b/tsconfig.json index a273b0c..cf4030a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,7 +18,8 @@ "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, - "jsx": "react-jsx" + "jsx": "react-jsx", + "baseUrl": "src" }, "include": [ "src" diff --git a/yarn.lock b/yarn.lock index c71690d..85a3764 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"