diff --git a/src/App.tsx b/src/App.tsx
index 0c3d8cb..7a08ace 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,13 +1,19 @@
import React from 'react';
+import { Routes, Route } from "react-router-dom";
import { BreakpointProvider } from './components/Breakpoint'
-import { Header } from './components/Layout/Header'
-import { Footer } from './components/Layout/Footer'
+import { Layout } from './components/Layout'
+import { Home } from './pages/activation/home'
+import { Login } from './pages/activation/login'
function App() {
return (
-
-
+
+ }>
+ } />
+ } />
+
+
);
}
diff --git a/src/components/Breakpoint/useBreakpoint.ts b/src/components/Breakpoint/useBreakpoint.ts
index 25ed1a8..ea3b406 100644
--- a/src/components/Breakpoint/useBreakpoint.ts
+++ b/src/components/Breakpoint/useBreakpoint.ts
@@ -66,7 +66,7 @@ export const useBreakpoint = (props: BreakpointProps): boolean => {
return size.width === breakpointList[key as keyof typeof breakpointList]
}
if (key === 'width') {
- return matchWidth(3000, props.width?.min, props.width?.max)
+ return matchWidth(size.width, props.width?.min, props.width?.max)
}
if (key === 'landscape' || key === 'portrait') {
return key === orientation
diff --git a/src/components/FormControl/ErrorMessage.tsx b/src/components/FormControl/ErrorMessage.tsx
new file mode 100644
index 0000000..780e1fb
--- /dev/null
+++ b/src/components/FormControl/ErrorMessage.tsx
@@ -0,0 +1,20 @@
+import React, { FC, PropsWithChildren } from 'react'
+import { InfoCircleOutlined } from '@ant-design/icons'
+import styled from 'styled-components'
+
+const StyledIcon = styled(InfoCircleOutlined)`
+ display: inline-block;
+ margin-right: 6px;
+ font-size: 14px;
+ line-height: 16px;
+ vertical-align: top;
+
+ ${props => props.theme.breakpoints.down('s')} {
+ font-size: 8px;
+ margin-right: 4px;
+ line-height: 10px;
+ }
+`
+export const ErrorMessage: FC> = ({ message, children }) => {
+ return <>{children || message}>
+}
diff --git a/src/components/FormControl/LabelWithTooltip.tsx b/src/components/FormControl/LabelWithTooltip.tsx
new file mode 100644
index 0000000..6e25f0c
--- /dev/null
+++ b/src/components/FormControl/LabelWithTooltip.tsx
@@ -0,0 +1,31 @@
+import React, { FC, PropsWithChildren } from 'react'
+import { InfoCircleOutlined } from '@ant-design/icons'
+import { Tooltip } from '../Tooltip'
+import styled from 'styled-components'
+
+const StyledLabel = styled.span`
+ position: relative;
+ display: inline-block;
+ width: 100%;
+`
+
+const StyledIcon = styled(InfoCircleOutlined)`
+ position: absolute;
+ font-size: 16px;
+ color: #000022;
+ right: 0;
+`
+export const LabelWithTooltip: FC> = ({
+ primary,
+ children,
+ title
+ }) => {
+ return (
+
+ {children}
+
+
+
+
+ )
+}
diff --git a/src/components/FormControl/index.ts b/src/components/FormControl/index.ts
index 85ea2fb..13b043a 100644
--- a/src/components/FormControl/index.ts
+++ b/src/components/FormControl/index.ts
@@ -5,3 +5,5 @@ export * from './Select'
export * from './Chekbox'
export * from './Radio'
export * from './RadioGroup'
+export * from './ErrorMessage'
+export * from './LabelWithTooltip'
diff --git a/src/components/Layout/Footer.tsx b/src/components/Layout/Footer.tsx
index f97a06b..a92039a 100644
--- a/src/components/Layout/Footer.tsx
+++ b/src/components/Layout/Footer.tsx
@@ -119,12 +119,12 @@ const TinyFooter = styled.footer`
const StyledFooter: FC = ({ children }) => {
return (
<>
-
+
{children}
-
+
{children}
@@ -133,9 +133,9 @@ const StyledFooter: FC = ({ children }) => {
)
}
-export const Footer: FC = () => {
+export const Footer: FC<{ className?: string }> = (props) => {
return (
-
+
{
FooterLinks.map((link, index) => (
diff --git a/src/components/Layout/Header.tsx b/src/components/Layout/Header.tsx
index aef6df2..22d3e6a 100644
--- a/src/components/Layout/Header.tsx
+++ b/src/components/Layout/Header.tsx
@@ -20,7 +20,6 @@ const StyledHeader = styled.header`
border-bottom: 2px solid #000022;
}
`
-
const StyledLogo = styled(Logo)`
width: 171px;
height: 41px;
@@ -78,11 +77,12 @@ const StyledButton = styled(Button)`
height: 58px;
${props => props.theme.breakpoints.down(600)} {
- width: 160px;
+ width: 130px;
}
`
export interface HeaderProps {
+ className?: string
showMenu?: boolean
onClickMenu?: () => void
showActiveKit?: boolean;
@@ -101,10 +101,11 @@ export const Header: FC = ({
showMenu,
onClickMenu,
showActiveKit,
- activeKitHref
+ activeKitHref,
+ className
}) => {
return (
-
+
{showMenu &&
@@ -115,7 +116,8 @@ export const Header: FC = ({
Activate Kit
}
-
+
SHOP
diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx
new file mode 100644
index 0000000..0db6454
--- /dev/null
+++ b/src/components/Layout/Layout.tsx
@@ -0,0 +1,36 @@
+import React, { FC, PropsWithChildren } from 'react'
+import styled from 'styled-components'
+import { Header, HeaderProps } from './Header'
+import { Footer } from './Footer'
+import { Outlet } from 'react-router-dom'
+
+const StyledSection = styled.section`
+ display: flex;
+ min-height: 100vh;
+ flex-direction: column;
+`
+
+const StyledHeader = styled(Header)`
+ flex: 0 0 auto;
+`
+
+const StyledFooter = styled(Footer)`
+ flex: 0 0 auto;
+`
+
+const StyledMain = styled.main`
+ flex: auto;
+`
+
+export const Layout: FC>> = ({ children, ...headerProps }) => {
+ return (
+
+
+
+
+ {children}
+
+
+
+ )
+}
diff --git a/src/components/Layout/index.ts b/src/components/Layout/index.ts
new file mode 100644
index 0000000..19b8497
--- /dev/null
+++ b/src/components/Layout/index.ts
@@ -0,0 +1 @@
+export * from './Layout'
diff --git a/src/components/Tooltip/Tooltip.tsx b/src/components/Tooltip/Tooltip.tsx
index 051fd50..4a204f2 100644
--- a/src/components/Tooltip/Tooltip.tsx
+++ b/src/components/Tooltip/Tooltip.tsx
@@ -1,15 +1,19 @@
-import React, { FC, useCallback } from 'react'
+import React, { FC, ReactNode, useCallback } from 'react'
import { Tooltip as AntdTooltip, TooltipProps as AntdTooltipProps } from 'antd'
import { ReactComponent as CloseIcon } from '../../icons/close.svg'
import styled from 'styled-components'
-export type TooltipProps = AntdTooltipProps & { closeable?: boolean; primary?: boolean }
+export type TooltipProps = Omit & {
+ closeable?: boolean; primary?: boolean;
+ title: ReactNode
+}
const Close = styled.span`
- line-height: 17px;
+ line-height: 25px;
vertical-align: middle;
cursor: pointer;
padding-left: 4px;
+ display: inline-block;
> svg {
width: 14px;
@@ -27,7 +31,8 @@ export const Tooltip: FC = ({ closeable, primary, title, overlayCl
trigger="click"
overlayClassName={`health-tooltip${primary ? '-highlighted' : ''} ${overlayClassName || ''}`}
{...props}
- title={<>{title} {closeable ? : null}>}
+ title={<>{title} {closeable ?
+ : null}>}
/>
)
}
diff --git a/src/index.tsx b/src/index.tsx
index b6141c8..d9e296d 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client';
import { ConfigProvider } from 'antd'
import { Theme } from './theme'
import { ThemeProvider } from './theme/ThemeProvider'
+import { BrowserRouter } from "react-router-dom";
import App from './App';
import 'typeface-lato'
import 'antd/dist/reset.css';
@@ -13,10 +14,12 @@ const root = ReactDOM.createRoot(
);
root.render(
-
-
-
-
-
+
+
+
+
+
+
+
);
diff --git a/src/pages/activation/home/Home.tsx b/src/pages/activation/home/Home.tsx
new file mode 100644
index 0000000..889eee2
--- /dev/null
+++ b/src/pages/activation/home/Home.tsx
@@ -0,0 +1,20 @@
+import React, { FC } from 'react'
+import { StyledContainer, StyledImageWrapper, StyledMainContent, StyledTitle, StyledContent } from './styled'
+import { Divider } from 'antd'
+import { Button } from '../../../components/Button'
+
+export const Home: FC = () => {
+ return (
+
+
+
+ Welcome to iHealth CheckMeSafe!
+
+
+ Or
+
+
+
+
+ )
+}
diff --git a/src/pages/activation/home/images/homePic@2x.png b/src/pages/activation/home/images/homePic@2x.png
new file mode 100644
index 0000000..66c47d3
Binary files /dev/null and b/src/pages/activation/home/images/homePic@2x.png differ
diff --git a/src/pages/activation/home/index.ts b/src/pages/activation/home/index.ts
new file mode 100644
index 0000000..ccb93a9
--- /dev/null
+++ b/src/pages/activation/home/index.ts
@@ -0,0 +1 @@
+export * from './Home'
diff --git a/src/pages/activation/home/styled.tsx b/src/pages/activation/home/styled.tsx
new file mode 100644
index 0000000..859af27
--- /dev/null
+++ b/src/pages/activation/home/styled.tsx
@@ -0,0 +1,57 @@
+import React from 'react'
+import styled from 'styled-components'
+import Image from './images/homePic@2x.png'
+import { Title } from '../../../components/Typography'
+
+export const StyledContainer = styled.div`
+ display: flex;
+ flex-direction: row;
+ padding: 25px 0 25px 40px;
+
+ ${props => props.theme.breakpoints.down('s')} {
+ padding: 4px 23px;
+ flex-direction: column;
+ align-items: center;
+ }
+`
+export const StyledImageWrapper = styled.div`
+ width: 54%;
+ max-width: 737px;
+ height: 773px;
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-image: url(${Image});
+ flex: 0 0 auto;
+
+ ${props => props.theme.breakpoints.down('s')} {
+ width: 100%;
+ height: 393px;
+ max-width: 344px;
+ }
+`
+
+export const StyledMainContent = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ flex: auto;
+
+ ${props => props.theme.breakpoints.down('s')} {
+ width: 100%;
+ padding-top: 26px;
+ }
+`
+
+export const StyledTitle = styled(Title)`
+`
+export const StyledContent = styled.div`
+ margin-top: 44px;
+ width: 358px;
+
+ ${props => props.theme.breakpoints.down('s')} {
+ width: 100%;
+ margin-top: 26px;
+ }
+`
diff --git a/src/pages/activation/login/ForgetPasswordModal.tsx b/src/pages/activation/login/ForgetPasswordModal.tsx
new file mode 100644
index 0000000..8484d48
--- /dev/null
+++ b/src/pages/activation/login/ForgetPasswordModal.tsx
@@ -0,0 +1,209 @@
+import React, { FC, useState } from 'react'
+import styled from 'styled-components'
+import { Modal, ModalProps } from '../../../components/Modal'
+import { Title, Paragraph, Link } from '../../../components/Typography'
+import { Form } from 'antd'
+import { Button } from '../../../components/Button'
+import { Input, Password, ErrorMessage, LabelWithTooltip } from '../../../components/FormControl'
+import Image from './images/password@2x.png'
+
+// TODO: Captcha
+type Props = Pick & {
+ onClose?: () => void
+}
+
+const StyledWrapper = styled.div`
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ min-height: 366px;
+
+ ${props => props.theme.breakpoints.up('s')} {
+ justify-content: center;
+ }
+`
+const StyledMain = styled.div`
+ width: 100%;
+
+ ${props => props.theme.breakpoints.up('s')} {
+ width: 350px;
+ }
+
+ ${props => props.theme.breakpoints.down('s')} {
+ padding-top: 37px;
+ }
+
+ .ant-form-item-required {
+ width: 100%;
+ }
+`
+const StyledTitle = styled(Title)`
+ margin-bottom: 2px !important;
+
+ ${props => props.theme.breakpoints.down('s')} {
+ margin-bottom: 5px !important;
+ }
+`
+const StyledCaptchaHint = styled(Paragraph)`
+ margin-top: 30px;
+`
+const TitleCopies = [
+ 'Forgot Password',
+ 'Reset Password',
+ 'Reset Password',
+ 'Password Reset'
+]
+
+const DescriptionCopies = [
+ 'Enter your email address to reset your password.',
+ 'A 6-digit code has been sent to John.smith@gmail.com',
+]
+
+export const ForgetPasswordModal: FC = ({ onClose, ...props }) => {
+ const [step, setStep] = useState(0)
+ const [email, setEmail] = useState('')
+ const [captcha, setCaptcha] = useState('')
+ const [expiration, setExpiration] = useState('9:45')
+ const handleFinishEmail = ({ email }: { email: string }) => {
+ setEmail(email)
+ setStep(1)
+ }
+
+ const handleFinishCaptcha = ({ captcha }: { captcha: string }) => {
+ setCaptcha(captcha)
+ setStep(2)
+ }
+
+ const handleSendAgain = () => {
+ // TODO: Update expiration
+
+ }
+
+ const handleConfirm = (data: { password: string }) => {
+ console.log(data)
+ setStep(3)
+ }
+
+ const handleClose = () => {
+ setStep(0)
+ setEmail('')
+ setCaptcha('')
+
+ onClose && onClose()
+ }
+
+ const handleLogin = () => {
+ // TODO: login
+ }
+ return (
+
+
+
+ {TitleCopies[step]}
+ {step < 2 && {DescriptionCopies[step]}}
+ {step === 0 ? (
+
+ },
+ { type: 'email', message: }
+ ]}
+ >
+
+
+
+
+
+
+ ) : null}
+ {step === 1 ? (
+
+ },
+ ]}
+ >
+
+
+
+ The code will expire in {expiration}
+ Didn’t receive the code? Send again
+
+
+
+
+
+ ) : null}
+ {step === 2 ? (
+
+ 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'));
+ },
+ }),
+ ]}
+ >
+
+
+
+
+
+
+ ) : null}
+ {step === 3 ? (
+ <>
+
+ 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
new file mode 100644
index 0000000..3662a44
--- /dev/null
+++ b/src/pages/activation/login/Login.tsx
@@ -0,0 +1,75 @@
+import React, { FC, useState } from 'react'
+import {
+ StyledContainer,
+ StyledImageWrapper,
+ StyledMainContent,
+ StyledHeadline,
+ StyledContent,
+ StyledButton,
+ StyledTitle, StyledHint, StyledHelp
+} from './styled'
+import { Form } from 'antd'
+import { Paragraph, Link } from '../../../components/Typography'
+import { Input, Password, ErrorMessage } from '../../../components/FormControl'
+import { ForgetPasswordModal } from './ForgetPasswordModal'
+
+export const Login: FC = () => {
+ const [showModal, setModal] = useState(false)
+ const handleShowModal = () => {
+ setModal(true)
+ }
+
+ const handleCloseModal = () => {
+ setModal(false)
+ }
+ const onFinish = (values: any) => {
+ // TODO: login
+ console.log('Success:', values);
+ };
+ return (
+
+
+
+ Welcome Back!
+ Log in to continue
+
+
+ },
+ { type: 'email', message: }
+ ]}
+ >
+
+
+ }]}
+ extra={Forgot password}
+ >
+
+
+
+ Log in
+
+
+
+
+ Don’t have an account?
+ Activate your kit
+
+
+
+
+
+
+ )
+}
diff --git a/src/pages/activation/login/images/loginPic@2x.png b/src/pages/activation/login/images/loginPic@2x.png
new file mode 100644
index 0000000..872d12e
Binary files /dev/null and b/src/pages/activation/login/images/loginPic@2x.png differ
diff --git a/src/pages/activation/login/images/password@2x.png b/src/pages/activation/login/images/password@2x.png
new file mode 100644
index 0000000..24b3a3a
Binary files /dev/null and b/src/pages/activation/login/images/password@2x.png differ
diff --git a/src/pages/activation/login/index.ts b/src/pages/activation/login/index.ts
new file mode 100644
index 0000000..8d03bcf
--- /dev/null
+++ b/src/pages/activation/login/index.ts
@@ -0,0 +1 @@
+export * from './Login'
diff --git a/src/pages/activation/login/styled.tsx b/src/pages/activation/login/styled.tsx
new file mode 100644
index 0000000..fe62e23
--- /dev/null
+++ b/src/pages/activation/login/styled.tsx
@@ -0,0 +1,80 @@
+import React from 'react'
+import styled from 'styled-components'
+import Image from './images/loginPic@2x.png'
+import { Title } from '../../../components/Typography'
+import { Button } from '../../../components/Button'
+
+export const StyledContainer = styled.div`
+ display: flex;
+ flex-direction: row;
+ padding: 25px 0 25px 40px;
+
+ ${props => props.theme.breakpoints.down('s')} {
+ padding: 16px;
+ flex-direction: column;
+ align-items: center;
+ }
+`
+export const StyledImageWrapper = styled.div`
+ width: 54%;
+ max-width: 737px;
+ height: 803px;
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-image: url(${Image});
+ flex: 0 0 auto;
+
+ ${props => props.theme.breakpoints.down('s')} {
+ display: none;
+ }
+`
+
+export const StyledMainContent = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-direction: column;
+ flex: auto;
+
+ ${props => props.theme.breakpoints.down('s')} {
+ width: 100%;
+ padding-top: 50px;
+ }
+`
+
+export const StyledHeadline = styled(Title)`
+ margin-bottom: 10px !important;
+ font-size: 36px;
+
+ ${props => props.theme.breakpoints.down('s')} {
+ margin-bottom: 4px !important;
+ }
+`
+
+export const StyledTitle = styled(Title)`
+ margin-top: 0 !important;
+`
+export const StyledContent = styled.div`
+ margin-top: 52px;
+ width: 358px;
+
+ ${props => props.theme.breakpoints.down('s')} {
+ width: 100%;
+ margin-top: 47px;
+ }
+`
+
+export const StyledButton = styled(Button)`
+ margin-top: 30px;
+`
+
+export const StyledHelp = styled(Button)`
+ padding: 0;
+ font-size: 16px;
+`
+
+export const StyledHint = styled.div`
+ text-align: center;
+ margin-top: 33px;
+`
diff --git a/src/styles/reset.css b/src/styles/reset.css
index bf404f6..5b0df83 100644
--- a/src/styles/reset.css
+++ b/src/styles/reset.css
@@ -18,6 +18,7 @@ html, body {
.ant-btn-link, .ant-btn-text {
font-size: 12px;
line-height: 16px;
+ height: 16px;
}
.ant-wave {
@@ -48,6 +49,7 @@ html, body {
}
.health-tooltip-highlighted.ant-tooltip .ant-tooltip-inner {
+ display: inline-flex;
position: relative;
box-shadow: 1px 1px 0px 0 #FF5A0C, -1px -1px 0px 0 #FF5A0C, -1px 1px 0px 0 #FF5A0C, 1px -1px 0px 0 #FF5A0C;
}
@@ -58,6 +60,7 @@ html, body {
.health-tooltip.ant-tooltip .ant-tooltip-inner {
position: relative;
+ display: inline-flex;
box-shadow: 0 2px 0px 0 #000022, 0 2px 0px 0px #000022, 1px 1px 0px 2px #000022;
}
@@ -191,3 +194,27 @@ html, body {
.ant-form-item {
margin-bottom: 15px;
}
+
+
+@media (min-width: 500px) {
+ .ant-form-item {
+ margin-bottom: 11px;
+ }
+}
+
+
+@media (max-width: 500px) {
+ .ant-form-item .ant-form-item-explain-error {
+ font-size: 10px;
+ }
+
+
+ .ant-modal .ant-modal-content {
+ padding-left: 18px;
+ padding-right: 18px;
+ }
+}
+
+.ant-form-item .ant-form-item-label > label .ant-form-item-tooltip {
+ color: #000022;
+}
diff --git a/src/theme/index.ts b/src/theme/index.ts
index 139de25..c65805b 100644
--- a/src/theme/index.ts
+++ b/src/theme/index.ts
@@ -1,4 +1,5 @@
import { ThemeConfig } from 'antd'
+
export const breakpointList = {
xs: 0,
s: 500,
@@ -53,6 +54,11 @@ export const Theme: ThemeConfig = {
colorBgTextHover: 'transparent',
colorBgTextActive: 'transparent',
},
+ Divider: {
+ colorSplit: '#000',
+ colorText: '#000',
+ fontSize: 20,
+ },
Modal: {
borderRadiusLG: 10,
boxShadow: '2px 2px 4px 1px rgba(0, 0, 0, 0.2)',