a70a9281eb
* merge squashed - A couple of CJS modules into ESM (config mostly) - Vite complains about node.js modules: fixed `useIsMatchingLocation.ts` > or use rollupOptions in vite.config.ts > ref: https://github.com/saleor/saleor-dashboard/blob/f0e4f59d97e2a8c3e22bd2af7b7ce68a361fc9a4/vite.config.js#L6 - Adjust Storybook to work with Vite: use @storybook/test - Use SWC for jest tranformations - Remove unused deps: - ts-jest: replaced with @swc/jest, typecheck by `tsc` - babel plugins - @svgr/plugin-jsx: not used - @testing-library/user-event: handled by @storybook/test - @typescript-eslint/utils: was not plugged in - tsup, esbuild-plugin-svgr: will look into that later - Install Vite required deps, and remove craco/webpack deps - Adjust SVG to work with Vite as components - Fixed `Step.tsx`: I dont know if one should be swaped for the other, but there should be no slash - Initial formating and linting: - removed empty object params - sorting imports, etc.. * prettier: fix pattern * coverage: sb coverage report isnt working * Add missing pieces * `yarn lint --fix` * fix: scripts permissions * tsc: cut errors in half * fix: remove `react-app-env.d.ts` * tsc: all fixed, except `react-data-grid` types issue * eslint: ignore env-config.js * eslint: Align ci with config * msw: bypass testing warnings ref: https://stackoverflow.com/questions/68024935/msw-logging-warnings-for-unhandled-supertest-requests * rebase: and fix things * Adjust to current `graphql-codegen` no ESM support * Remove vite plugin and use built-in methods * rebase: and some fixes * quick fix + `corepack use yarn@1.22.19` * Fix build errors --------- Co-authored-by: Charles Bochet <charles@twenty.com>
236 lines
6.9 KiB
TypeScript
236 lines
6.9 KiB
TypeScript
import { useMemo } from 'react';
|
|
import { Controller } from 'react-hook-form';
|
|
import { useTheme } from '@emotion/react';
|
|
import styled from '@emotion/styled';
|
|
import { motion } from 'framer-motion';
|
|
|
|
import { IconGoogle } from '@/ui/display/icon/components/IconGoogle';
|
|
import { MainButton } from '@/ui/input/button/components/MainButton';
|
|
import { TextInput } from '@/ui/input/components/TextInput';
|
|
import { AnimatedEaseIn } from '@/ui/utilities/animation/components/AnimatedEaseIn';
|
|
|
|
import { Logo } from '../../components/Logo';
|
|
import { Title } from '../../components/Title';
|
|
import { SignInUpMode, SignInUpStep, useSignInUp } from '../hooks/useSignInUp';
|
|
|
|
import { FooterNote } from './FooterNote';
|
|
import { HorizontalSeparator } from './HorizontalSeparator';
|
|
|
|
const StyledContentContainer = styled.div`
|
|
margin-bottom: ${({ theme }) => theme.spacing(8)};
|
|
margin-top: ${({ theme }) => theme.spacing(4)};
|
|
width: 200px;
|
|
`;
|
|
|
|
const StyledFooterNote = styled(FooterNote)`
|
|
max-width: 280px;
|
|
`;
|
|
|
|
const StyledForm = styled.form`
|
|
align-items: center;
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: 100%;
|
|
`;
|
|
|
|
const StyledFullWidthMotionDiv = styled(motion.div)`
|
|
width: 100%;
|
|
`;
|
|
|
|
const StyledInputContainer = styled.div`
|
|
margin-bottom: ${({ theme }) => theme.spacing(3)};
|
|
`;
|
|
|
|
export const SignInUpForm = () => {
|
|
const {
|
|
authProviders,
|
|
signInWithGoogle,
|
|
signInUpStep,
|
|
signInUpMode,
|
|
showErrors,
|
|
setShowErrors,
|
|
continueWithCredentials,
|
|
continueWithEmail,
|
|
submitCredentials,
|
|
form: {
|
|
control,
|
|
watch,
|
|
handleSubmit,
|
|
formState: { isSubmitting },
|
|
},
|
|
workspace,
|
|
} = useSignInUp();
|
|
|
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
if (event.key === 'Enter') {
|
|
event.preventDefault();
|
|
|
|
if (signInUpStep === SignInUpStep.Init) {
|
|
continueWithEmail();
|
|
} else if (signInUpStep === SignInUpStep.Email) {
|
|
continueWithCredentials();
|
|
} else if (signInUpStep === SignInUpStep.Password) {
|
|
setShowErrors(true);
|
|
handleSubmit(submitCredentials)();
|
|
}
|
|
}
|
|
};
|
|
|
|
const buttonTitle = useMemo(() => {
|
|
if (signInUpStep === SignInUpStep.Init) {
|
|
return 'Continue With Email';
|
|
}
|
|
|
|
if (signInUpStep === SignInUpStep.Email) {
|
|
return 'Continue';
|
|
}
|
|
|
|
return signInUpMode === SignInUpMode.SignIn ? 'Sign in' : 'Sign up';
|
|
}, [signInUpMode, signInUpStep]);
|
|
|
|
const title = useMemo(() => {
|
|
if (signInUpMode === SignInUpMode.Invite) {
|
|
return `Join ${workspace?.displayName ?? ''} team`;
|
|
}
|
|
|
|
return signInUpMode === SignInUpMode.SignIn
|
|
? 'Sign in to Twenty'
|
|
: 'Sign up to Twenty';
|
|
}, [signInUpMode, workspace?.displayName]);
|
|
|
|
const theme = useTheme();
|
|
|
|
return (
|
|
<>
|
|
<AnimatedEaseIn>
|
|
<Logo workspaceLogo={workspace?.logo} />
|
|
</AnimatedEaseIn>
|
|
<Title animate>{title}</Title>
|
|
<StyledContentContainer>
|
|
{authProviders.google && (
|
|
<>
|
|
<MainButton
|
|
Icon={() => <IconGoogle size={theme.icon.size.lg} />}
|
|
title="Continue with Google"
|
|
onClick={signInWithGoogle}
|
|
fullWidth
|
|
/>
|
|
<HorizontalSeparator />
|
|
</>
|
|
)}
|
|
|
|
<StyledForm
|
|
onSubmit={(event) => {
|
|
event.preventDefault();
|
|
}}
|
|
>
|
|
{signInUpStep !== SignInUpStep.Init && (
|
|
<StyledFullWidthMotionDiv
|
|
initial={{ opacity: 0, height: 0 }}
|
|
animate={{ opacity: 1, height: 'auto' }}
|
|
transition={{
|
|
type: 'spring',
|
|
stiffness: 800,
|
|
damping: 35,
|
|
}}
|
|
>
|
|
<Controller
|
|
name="email"
|
|
control={control}
|
|
render={({
|
|
field: { onChange, onBlur, value },
|
|
fieldState: { error },
|
|
}) => (
|
|
<StyledInputContainer>
|
|
<TextInput
|
|
autoFocus
|
|
value={value}
|
|
placeholder="Email"
|
|
onBlur={onBlur}
|
|
onChange={(value: string) => {
|
|
onChange(value);
|
|
if (signInUpStep === SignInUpStep.Password) {
|
|
continueWithEmail();
|
|
}
|
|
}}
|
|
error={showErrors ? error?.message : undefined}
|
|
onKeyDown={handleKeyDown}
|
|
fullWidth
|
|
disableHotkeys
|
|
/>
|
|
</StyledInputContainer>
|
|
)}
|
|
/>
|
|
</StyledFullWidthMotionDiv>
|
|
)}
|
|
{signInUpStep === SignInUpStep.Password && (
|
|
<StyledFullWidthMotionDiv
|
|
initial={{ opacity: 0, height: 0 }}
|
|
animate={{ opacity: 1, height: 'auto' }}
|
|
transition={{
|
|
type: 'spring',
|
|
stiffness: 800,
|
|
damping: 35,
|
|
}}
|
|
>
|
|
<Controller
|
|
name="password"
|
|
control={control}
|
|
render={({
|
|
field: { onChange, onBlur, value },
|
|
fieldState: { error },
|
|
}) => (
|
|
<StyledInputContainer>
|
|
<TextInput
|
|
autoFocus
|
|
value={value}
|
|
type="password"
|
|
placeholder="Password"
|
|
onBlur={onBlur}
|
|
onChange={onChange}
|
|
onKeyDown={handleKeyDown}
|
|
error={showErrors ? error?.message : undefined}
|
|
fullWidth
|
|
disableHotkeys
|
|
/>
|
|
</StyledInputContainer>
|
|
)}
|
|
/>
|
|
</StyledFullWidthMotionDiv>
|
|
)}
|
|
|
|
<MainButton
|
|
variant="secondary"
|
|
title={buttonTitle}
|
|
type="submit"
|
|
onClick={() => {
|
|
if (signInUpStep === SignInUpStep.Init) {
|
|
continueWithEmail();
|
|
return;
|
|
}
|
|
if (signInUpStep === SignInUpStep.Email) {
|
|
continueWithCredentials();
|
|
return;
|
|
}
|
|
setShowErrors(true);
|
|
handleSubmit(submitCredentials)();
|
|
}}
|
|
disabled={
|
|
SignInUpStep.Init
|
|
? false
|
|
: signInUpStep === SignInUpStep.Email
|
|
? !watch('email')
|
|
: !watch('email') || !watch('password') || isSubmitting
|
|
}
|
|
fullWidth
|
|
/>
|
|
</StyledForm>
|
|
</StyledContentContainer>
|
|
<StyledFooterNote>
|
|
By using Twenty, you agree to the Terms of Service and Data Processing
|
|
Agreement.
|
|
</StyledFooterNote>
|
|
</>
|
|
);
|
|
};
|