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 (
+
+
+
+ );
+}
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
+
+
+
+
+ }
+ onClick={onGoogleLoginClick}
+ />
+
+ console.log(value)}
+ fullWidth={true}
+ />
+
+
+
+ By using Twenty, you agree to the Terms of Service and Data Processing
+ Agreement.
+
+
>
);
}