From 5904a393620144c4ad7bac16f3e24680d3d0ddff Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Sun, 18 Jun 2023 23:51:59 +0200 Subject: [PATCH] Design Auth index (#325) --- front/package.json | 4 +- .../modules/auth/components/FooterNote.tsx | 20 +++++++ .../auth/components/HorizontalSeparator.tsx | 13 +++++ .../modules/auth/components/InputLabel.tsx | 12 +++++ front/src/modules/auth/components/Logo.tsx | 19 +++++++ front/src/modules/auth/components/Modal.tsx | 25 +++++++++ .../modules/auth/components/RequireAuth.tsx | 2 +- .../src/modules/auth/components/SubTitle.tsx | 11 ++++ front/src/modules/auth/components/Title.tsx | 15 ++++++ .../ui/components/buttons/PrimaryButton.tsx | 52 +++++++++++++++++++ .../ui/components/buttons/SecondaryButton.tsx | 44 ++++++++++++++++ .../ui/components/buttons/TertiaryButton.tsx | 11 ++++ .../ui/components/inputs/TextInput.tsx | 49 +++++++++++++++++ .../src/modules/ui/components/modal/Modal.tsx | 3 +- front/src/modules/ui/icons/index.ts | 1 + front/src/modules/ui/layout/styles/texts.ts | 2 + front/src/pages/auth/Index.tsx | 49 +++++++++++++++-- 17 files changed, 325 insertions(+), 7 deletions(-) create mode 100644 front/src/modules/auth/components/FooterNote.tsx create mode 100644 front/src/modules/auth/components/HorizontalSeparator.tsx create mode 100644 front/src/modules/auth/components/InputLabel.tsx create mode 100644 front/src/modules/auth/components/Logo.tsx create mode 100644 front/src/modules/auth/components/Modal.tsx create mode 100644 front/src/modules/auth/components/SubTitle.tsx create mode 100644 front/src/modules/auth/components/Title.tsx create mode 100644 front/src/modules/ui/components/buttons/PrimaryButton.tsx create mode 100644 front/src/modules/ui/components/buttons/SecondaryButton.tsx create mode 100644 front/src/modules/ui/components/buttons/TertiaryButton.tsx create mode 100644 front/src/modules/ui/components/inputs/TextInput.tsx diff --git a/front/package.json b/front/package.json index d09ecfa22..de5ef67d2 100644 --- a/front/package.json +++ b/front/package.json @@ -143,8 +143,8 @@ "workerDirectory": "public" }, "nyc": { - "lines": 65, - "statements": 65, + "lines": 70, + "statements": 70, "exclude": [ "src/generated/**/*" ] diff --git a/front/src/modules/auth/components/FooterNote.tsx b/front/src/modules/auth/components/FooterNote.tsx new file mode 100644 index 000000000..d95015b96 --- /dev/null +++ b/front/src/modules/auth/components/FooterNote.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import styled from '@emotion/styled'; + +type OwnProps = { + children: React.ReactNode; +}; + +const StyledContainer = styled.div` + align-items: center; + color: ${({ theme }) => theme.text40}; + display: flex; + font-size: ${({ theme }) => theme.fontSizeSmall}px; + padding-left: ${({ theme }) => theme.spacing(14)}; + padding-right: ${({ theme }) => theme.spacing(14)}; + text-align: center; +`; + +export function FooterNote({ children }: OwnProps): JSX.Element { + return {children}; +} diff --git a/front/src/modules/auth/components/HorizontalSeparator.tsx b/front/src/modules/auth/components/HorizontalSeparator.tsx new file mode 100644 index 000000000..1e0463029 --- /dev/null +++ b/front/src/modules/auth/components/HorizontalSeparator.tsx @@ -0,0 +1,13 @@ +import styled from '@emotion/styled'; + +const Separator = styled.div` + background-color: ${(props) => props.theme.mediumBorder}; + height: 1px; + margin-bottom: ${(props) => props.theme.spacing(3)}; + margin-top: ${(props) => props.theme.spacing(3)}; + width: 100%; +`; + +export function HorizontalSeparator(): JSX.Element { + return ; +} diff --git a/front/src/modules/auth/components/InputLabel.tsx b/front/src/modules/auth/components/InputLabel.tsx new file mode 100644 index 000000000..5be3c97c7 --- /dev/null +++ b/front/src/modules/auth/components/InputLabel.tsx @@ -0,0 +1,12 @@ +import styled from '@emotion/styled'; + +type OwnProps = { + label: string; + subLabel?: string; +}; + +const StyledContainer = styled.div``; + +export function SubTitle({ label, subLabel }: OwnProps): JSX.Element { + return {label}; +} diff --git a/front/src/modules/auth/components/Logo.tsx b/front/src/modules/auth/components/Logo.tsx new file mode 100644 index 000000000..960250069 --- /dev/null +++ b/front/src/modules/auth/components/Logo.tsx @@ -0,0 +1,19 @@ +import styled from '@emotion/styled'; + +const StyledLogo = styled.div` + height: 40px; + width: 40px; + + img { + height: 100%; + width: 100%; + } +`; + +export function Logo(): JSX.Element { + return ( + + logo + + ); +} diff --git a/front/src/modules/auth/components/Modal.tsx b/front/src/modules/auth/components/Modal.tsx new file mode 100644 index 000000000..d83119940 --- /dev/null +++ b/front/src/modules/auth/components/Modal.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import styled from '@emotion/styled'; + +import { Modal as UIModal } from '@/ui/components/modal/Modal'; + +type OwnProps = { + children: React.ReactNode; +}; + +const StyledContainer = styled.div` + align-items: center; + display: flex; + flex-direction: column; + padding-bottom: ${({ theme }) => theme.spacing(10)}; + padding-top: ${({ theme }) => theme.spacing(10)}; + width: 400px; +`; + +export function Modal({ children }: OwnProps): JSX.Element { + return ( + + {children} + + ); +} diff --git a/front/src/modules/auth/components/RequireAuth.tsx b/front/src/modules/auth/components/RequireAuth.tsx index d699b2bfe..4781e0faa 100644 --- a/front/src/modules/auth/components/RequireAuth.tsx +++ b/front/src/modules/auth/components/RequireAuth.tsx @@ -36,7 +36,7 @@ export function RequireAuth({ useEffect(() => { if (!hasAccessToken()) { - navigate('/auth/login'); + navigate('/auth'); } }, [navigate]); diff --git a/front/src/modules/auth/components/SubTitle.tsx b/front/src/modules/auth/components/SubTitle.tsx new file mode 100644 index 000000000..9348eb1a1 --- /dev/null +++ b/front/src/modules/auth/components/SubTitle.tsx @@ -0,0 +1,11 @@ +import styled from '@emotion/styled'; + +type OwnProps = { + subTitle: string; +}; + +const StyledSubTitle = styled.div``; + +export function SubTitle({ subTitle }: OwnProps): JSX.Element { + return {subTitle}; +} diff --git a/front/src/modules/auth/components/Title.tsx b/front/src/modules/auth/components/Title.tsx new file mode 100644 index 000000000..cfcaafbf7 --- /dev/null +++ b/front/src/modules/auth/components/Title.tsx @@ -0,0 +1,15 @@ +import styled from '@emotion/styled'; + +type OwnProps = { + title: string; +}; + +const StyledTitle = styled.div` + font-size: ${({ theme }) => theme.fontSizeExtraLarge}; + font-weight: ${({ theme }) => theme.fontWeightSemibold}; + margin-top: ${({ theme }) => theme.spacing(10)}; +`; + +export function Title({ title }: OwnProps): JSX.Element { + return {title}; +} diff --git a/front/src/modules/ui/components/buttons/PrimaryButton.tsx b/front/src/modules/ui/components/buttons/PrimaryButton.tsx new file mode 100644 index 000000000..fb26783e0 --- /dev/null +++ b/front/src/modules/ui/components/buttons/PrimaryButton.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import styled from '@emotion/styled'; + +type OwnProps = { + label: string; + icon?: React.ReactNode; + fullWidth?: boolean; + onClick?: () => void; +}; + +const StyledButton = styled.button<{ fullWidth: boolean }>` + align-items: center; + background: radial-gradient( + 50% 62.62% at 50% 0%, + ${({ theme }) => theme.text60} 0%, + ${({ theme }) => theme.text80} 100% + ); + border: 1px solid ${({ theme }) => theme.primaryBorder}; + border-radius: 8px; + box-shadow: 0px 0px 4px ${({ theme }) => theme.mediumBackgroundTransparent} 0%, + 0px 2px 4px ${({ theme }) => theme.lightBackgroundTransparent} 0%; + color: ${(props) => props.theme.text0}; + cursor: pointer; + display: flex; + flex-direction: row; + font-weight: ${({ theme }) => theme.fontWeightBold}; + gap: 8px; + justify-content: center; + padding: ${({ theme }) => theme.spacing(2)} ${({ theme }) => theme.spacing(3)}; + width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')}; +`; + +export function PrimaryButton({ + label, + icon, + fullWidth, + onClick, +}: OwnProps): JSX.Element { + return ( + { + if (onClick) { + onClick(); + } + }} + > + {icon} + {label} + + ); +} diff --git a/front/src/modules/ui/components/buttons/SecondaryButton.tsx b/front/src/modules/ui/components/buttons/SecondaryButton.tsx new file mode 100644 index 000000000..af8664b7d --- /dev/null +++ b/front/src/modules/ui/components/buttons/SecondaryButton.tsx @@ -0,0 +1,44 @@ +import React from 'react'; +import styled from '@emotion/styled'; + +type OwnProps = { + label: string; + icon?: React.ReactNode; + fullWidth?: boolean; +}; + +const StyledButton = styled.button<{ fullWidth: boolean }>` + align-items: center; + background: ${({ theme }) => theme.primaryBackground}; + border: 1px solid ${({ theme }) => theme.primaryBorder}; + border-radius: 8px; + box-shadow: 0px 0px 4px ${({ theme }) => theme.mediumBackgroundTransparent} 0%, + 0px 2px 4px ${({ theme }) => theme.lightBackgroundTransparent} 0%; + color: ${(props) => props.theme.text80}; + cursor: pointer; + display: flex; + flex-direction: row; + font-weight: ${({ theme }) => theme.fontWeightBold}; + gap: 8px; + justify-content: center; + padding: ${({ theme }) => theme.spacing(2)} ${({ theme }) => theme.spacing(3)}; + padding: 8px 32px; + width: ${({ fullWidth }) => (fullWidth ? '100%' : 'auto')}; + + &:hover { + background: ${({ theme }) => theme.tertiaryBackground}; + } +`; + +export function SecondaryButton({ + label, + icon, + fullWidth, +}: OwnProps): JSX.Element { + return ( + + {icon} + {label} + + ); +} diff --git a/front/src/modules/ui/components/buttons/TertiaryButton.tsx b/front/src/modules/ui/components/buttons/TertiaryButton.tsx new file mode 100644 index 000000000..dfaccc9e6 --- /dev/null +++ b/front/src/modules/ui/components/buttons/TertiaryButton.tsx @@ -0,0 +1,11 @@ +import styled from '@emotion/styled'; + +type OwnProps = { + text: string; +}; + +const StyledButton = styled.button``; + +export function TertiaryButton({ text }: OwnProps): JSX.Element { + return {text}; +} diff --git a/front/src/modules/ui/components/inputs/TextInput.tsx b/front/src/modules/ui/components/inputs/TextInput.tsx new file mode 100644 index 000000000..064382cb9 --- /dev/null +++ b/front/src/modules/ui/components/inputs/TextInput.tsx @@ -0,0 +1,49 @@ +import { ChangeEvent, useState } from 'react'; +import styled from '@emotion/styled'; + +type OwnProps = { + initialValue: string; + onChange: (text: string) => void; + fullWidth?: boolean; +}; + +const StyledInput = styled.input<{ fullWidth: boolean }>` + background-color: ${({ theme }) => theme.lighterBackgroundTransparent}; + border: 1px solid ${({ theme }) => theme.lightBorder}; + border-radius: 4px; + padding: ${({ theme }) => theme.spacing(2)} ${({ theme }) => + theme.spacing(3)}; + + color: ${({ theme }) => theme.text80}; + outline: none; + width: ${({ fullWidth, theme }) => + fullWidth ? `calc(100% - ${theme.spacing(6)})` : 'auto'}; + + &::placeholder, + &::-webkit-input-placeholder { + color: ${({ theme }) => theme.text30} + font-family: ${({ theme }) => theme.fontFamily};; + font-weight: ${({ theme }) => theme.fontWeightMedium}; + } + margin-bottom: ${({ theme }) => theme.spacing(3)}; +`; + +export function TextInput({ + initialValue, + onChange, + fullWidth, +}: OwnProps): JSX.Element { + const [value, setValue] = useState(initialValue); + + return ( + ) => { + setValue(event.target.value); + onChange(event.target.value); + }} + /> + ); +} diff --git a/front/src/modules/ui/components/modal/Modal.tsx b/front/src/modules/ui/components/modal/Modal.tsx index f51e44526..925f11a59 100644 --- a/front/src/modules/ui/components/modal/Modal.tsx +++ b/front/src/modules/ui/components/modal/Modal.tsx @@ -7,6 +7,7 @@ export function Modal({ children }: { children: React.ReactNode }) { return ( {children} diff --git a/front/src/modules/ui/icons/index.ts b/front/src/modules/ui/icons/index.ts index 5152ecb39..085c9b8b4 100644 --- a/front/src/modules/ui/icons/index.ts +++ b/front/src/modules/ui/icons/index.ts @@ -27,3 +27,4 @@ export { IconArrowNarrowDown } from '@tabler/icons-react'; export { IconArrowNarrowUp } from '@tabler/icons-react'; export { IconArrowRight } from '@tabler/icons-react'; export { IconArrowUpRight } from '@tabler/icons-react'; +export { IconBrandGoogle } from '@tabler/icons-react'; diff --git a/front/src/modules/ui/layout/styles/texts.ts b/front/src/modules/ui/layout/styles/texts.ts index 25cf41e70..a52685fb8 100644 --- a/front/src/modules/ui/layout/styles/texts.ts +++ b/front/src/modules/ui/layout/styles/texts.ts @@ -3,6 +3,7 @@ export const commonText = { fontSizeSmall: '0.92rem', fontSizeMedium: '1rem', fontSizeLarge: '1.08rem', + fontSizeExtraLarge: '1.54rem', fontWeightMedium: 500, fontWeightSemibold: 600, @@ -12,4 +13,5 @@ export const commonText = { lineHeight: 1.5, iconSizeMedium: 16, + iconSizeSmall: 14, }; diff --git a/front/src/pages/auth/Index.tsx b/front/src/pages/auth/Index.tsx index c0be93629..51c965216 100644 --- a/front/src/pages/auth/Index.tsx +++ b/front/src/pages/auth/Index.tsx @@ -1,14 +1,31 @@ -import { useEffect } from 'react'; +import { useCallback, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { FooterNote } from '@/auth/components/FooterNote'; +import { HorizontalSeparator } from '@/auth/components/HorizontalSeparator'; +import { Logo } from '@/auth/components/Logo'; +import { Modal } from '@/auth/components/Modal'; +import { Title } from '@/auth/components/Title'; import { useMockData } from '@/auth/hooks/useMockData'; import { hasAccessToken } from '@/auth/services/AuthService'; -import { Modal } from '@/ui/components/modal/Modal'; +import { PrimaryButton } from '@/ui/components/buttons/PrimaryButton'; +import { SecondaryButton } from '@/ui/components/buttons/SecondaryButton'; +import { TextInput } from '@/ui/components/inputs/TextInput'; +import { IconBrandGoogle } from '@/ui/icons'; import { Companies } from '../companies/Companies'; +const StyledContentContainer = styled.div` + padding-bottom: ${({ theme }) => theme.spacing(8)}; + padding-top: ${({ theme }) => theme.spacing(8)}; + width: 200px; +`; + export function Index() { const navigate = useNavigate(); + const theme = useTheme(); useMockData(); useEffect(() => { @@ -17,10 +34,36 @@ export function Index() { } }, [navigate]); + const onGoogleLoginClick = useCallback(() => { + navigate('/auth/login'); + }, [navigate]); + return ( <> - Welcome to Twenty + + + + <StyledContentContainer> + <PrimaryButton + fullWidth={true} + label="Continue With Google" + icon={<IconBrandGoogle size={theme.iconSizeSmall} stroke={4} />} + onClick={onGoogleLoginClick} + /> + <HorizontalSeparator /> + <TextInput + initialValue="" + onChange={(value) => console.log(value)} + fullWidth={true} + /> + <SecondaryButton label="Continue" fullWidth={true} /> + </StyledContentContainer> + <FooterNote> + By using Twenty, you agree to the Terms of Service and Data Processing + Agreement. + </FooterNote> + </Modal> </> ); }