2
0

♻️ (builder) Change to features-centric folder structure

This commit is contained in:
Baptiste Arnaud
2022-11-15 09:35:48 +01:00
committed by Baptiste Arnaud
parent 3686465a85
commit 643571fe7d
683 changed files with 3907 additions and 3643 deletions

View File

@ -0,0 +1,14 @@
import { setUser } from '@sentry/nextjs'
import { User } from 'db'
import { NextApiRequest } from 'next'
import { getSession } from 'next-auth/react'
export const getAuthenticatedUser = async (
req: NextApiRequest
): Promise<User | undefined> => {
const session = await getSession({ req })
if (!session?.user || !('id' in session.user)) return
const user = session.user as User
setUser({ id: user.id, email: user.email ?? undefined })
return session?.user as User
}

View File

@ -0,0 +1,31 @@
import {
FlexProps,
Flex,
Box,
Divider,
Text,
useColorModeValue,
} from '@chakra-ui/react'
import React from 'react'
export const DividerWithText = (props: FlexProps) => {
const { children, ...flexProps } = props
return (
<Flex align="center" color="gray.300" {...flexProps}>
<Box flex="1">
<Divider borderColor="currentcolor" />
</Box>
<Text
as="span"
px="3"
color={useColorModeValue('gray.600', 'gray.400')}
fontWeight="medium"
>
{children}
</Text>
<Box flex="1">
<Divider borderColor="currentcolor" />
</Box>
</Flex>
)
}

View File

@ -0,0 +1,122 @@
import {
Button,
HTMLChakraProps,
Input,
Stack,
HStack,
Text,
Spinner,
} from '@chakra-ui/react'
import React, { ChangeEvent, FormEvent, useEffect } from 'react'
import { useState } from 'react'
import {
ClientSafeProvider,
getProviders,
LiteralUnion,
signIn,
useSession,
} from 'next-auth/react'
import { DividerWithText } from './DividerWithText'
import { SocialLoginButtons } from './SocialLoginButtons'
import { useRouter } from 'next/router'
import { BuiltInProviderType } from 'next-auth/providers'
import { useToast } from '@/hooks/useToast'
import { TextLink } from '@/components/TextLink'
type Props = {
defaultEmail?: string
}
export const SignInForm = ({
defaultEmail,
}: Props & HTMLChakraProps<'form'>) => {
const router = useRouter()
const { status } = useSession()
const [authLoading, setAuthLoading] = useState(false)
const [isLoadingProviders, setIsLoadingProviders] = useState(true)
const [emailValue, setEmailValue] = useState(defaultEmail ?? '')
const { showToast } = useToast()
const [providers, setProviders] =
useState<
Record<LiteralUnion<BuiltInProviderType, string>, ClientSafeProvider>
>()
const hasNoAuthProvider =
!isLoadingProviders && Object.keys(providers ?? {}).length === 0
useEffect(() => {
if (status === 'authenticated')
router.replace({ pathname: '/typebots', query: router.query })
;(async () => {
const providers = await getProviders()
setProviders(providers ?? undefined)
setIsLoadingProviders(false)
})()
}, [status, router])
const handleEmailChange = (e: ChangeEvent<HTMLInputElement>) =>
setEmailValue(e.target.value)
const handleEmailSubmit = async (e: FormEvent) => {
e.preventDefault()
setAuthLoading(true)
const response = await signIn('email', {
email: emailValue,
redirect: false,
})
response?.error
? showToast({
title: 'Unauthorized',
description: 'Sign ups are disabled.',
})
: showToast({
status: 'success',
title: 'Success!',
description: 'Check your inbox to sign in',
})
setAuthLoading(false)
}
if (isLoadingProviders) return <Spinner />
if (hasNoAuthProvider)
return (
<Text>
You need to{' '}
<TextLink
href="https://docs.typebot.io/self-hosting/configuration"
isExternal
>
configure at least one auth provider
</TextLink>{' '}
(Email, Google, GitHub, Facebook or Azure AD).
</Text>
)
return (
<Stack spacing="4" w="330px">
<SocialLoginButtons providers={providers} />
{providers?.email && (
<>
<DividerWithText mt="6">Or with your email</DividerWithText>
<HStack as="form" onSubmit={handleEmailSubmit}>
<Input
name="email"
type="email"
autoComplete="email"
placeholder="email@company.com"
required
value={emailValue}
onChange={handleEmailChange}
/>
<Button
type="submit"
isLoading={
['loading', 'authenticated'].includes(status) || authLoading
}
>
Submit
</Button>
</HStack>
</>
)}
</Stack>
)
}

View File

@ -0,0 +1,38 @@
import { Seo } from '@/components/Seo'
import { TextLink } from '@/components/TextLink'
import { VStack, Heading, Text } from '@chakra-ui/react'
import { useRouter } from 'next/router'
import { SignInForm } from './SignInForm'
type Props = {
type: 'signin' | 'signup'
defaultEmail?: string
}
export const SignInPage = ({ type }: Props) => {
const { query } = useRouter()
return (
<VStack spacing={4} h="100vh" justifyContent="center">
<Seo title={type === 'signin' ? 'Sign In' : 'Register'} />
<Heading
onClick={() => {
throw new Error('Sentry is working')
}}
>
{type === 'signin' ? 'Sign In' : 'Create an account'}
</Heading>
{type === 'signin' ? (
<Text>
Don't have an account?{' '}
<TextLink href="/register">Sign up for free</TextLink>
</Text>
) : (
<Text>
Already have an account? <TextLink href="/signin">Sign in</TextLink>
</Text>
)}
<SignInForm defaultEmail={query.g?.toString()} />
</VStack>
)
}

View File

@ -0,0 +1,110 @@
import { Stack, Button } from '@chakra-ui/react'
import { GithubIcon } from '@/components/icons'
import {
ClientSafeProvider,
LiteralUnion,
signIn,
useSession,
} from 'next-auth/react'
import { useRouter } from 'next/router'
import React from 'react'
import { stringify } from 'qs'
import { BuiltInProviderType } from 'next-auth/providers'
import { GoogleLogo } from '@/components/GoogleLogo'
import { AzureAdLogo, FacebookLogo, GitlabLogo } from './logos'
type Props = {
providers:
| Record<LiteralUnion<BuiltInProviderType, string>, ClientSafeProvider>
| undefined
}
export const SocialLoginButtons = ({ providers }: Props) => {
const { query } = useRouter()
const { status } = useSession()
const handleGitHubClick = async () =>
signIn('github', {
callbackUrl: `/typebots?${stringify(query)}`,
})
const handleGoogleClick = async () =>
signIn('google', {
callbackUrl: `/typebots?${stringify(query)}`,
})
const handleFacebookClick = async () =>
signIn('facebook', {
callbackUrl: `/typebots?${stringify(query)}`,
})
const handleGitlabClick = async () =>
signIn('gitlab', {
callbackUrl: `/typebots?${stringify(query)}`,
})
const handleAzureAdClick = async () =>
signIn('azure-ad', {
callbackUrl: `/typebots?${stringify(query)}`,
})
return (
<Stack>
{providers?.github && (
<Button
leftIcon={<GithubIcon />}
onClick={handleGitHubClick}
data-testid="github"
isLoading={['loading', 'authenticated'].includes(status)}
variant="outline"
>
Continue with GitHub
</Button>
)}
{providers?.google && (
<Button
leftIcon={<GoogleLogo />}
onClick={handleGoogleClick}
data-testid="google"
isLoading={['loading', 'authenticated'].includes(status)}
variant="outline"
>
Continue with Google
</Button>
)}
{providers?.facebook && (
<Button
leftIcon={<FacebookLogo />}
onClick={handleFacebookClick}
data-testid="facebook"
isLoading={['loading', 'authenticated'].includes(status)}
variant="outline"
>
Continue with Facebook
</Button>
)}
{providers?.gitlab && (
<Button
leftIcon={<GitlabLogo />}
onClick={handleGitlabClick}
data-testid="gitlab"
isLoading={['loading', 'authenticated'].includes(status)}
variant="outline"
>
Continue with {providers.gitlab.name}
</Button>
)}
{providers?.['azure-ad'] && (
<Button
leftIcon={<AzureAdLogo />}
onClick={handleAzureAdClick}
data-testid="azure-ad"
isLoading={['loading', 'authenticated'].includes(status)}
variant="outline"
>
Continue with {providers['azure-ad'].name}
</Button>
)}
</Stack>
)
}

View File

@ -0,0 +1,31 @@
import { Icon, IconProps } from '@chakra-ui/react'
export const AzureAdLogo = (props: IconProps) => {
return (
<Icon
id="svg1035"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 374.5 377.3"
{...props}
>
<g id="layer1" transform="translate(-39.022 -78.115)">
<g id="g1016" transform="translate(-63.947 -88.179)">
<path
id="path1008"
fill='#00bef2'
d="M290 166.3c.4 0 .8.5 1.4 1.4.5.8 42.6 51.3 93.6 112.2 51 60.9 92.6 111 92.4 111.3-.1.3-40.7 33.6-90.2 73.9s-91.6 74.6-93.5 76.2c-3.3 2.7-3.5 2.8-4.7 1.6-.7-.7-42.9-35.2-93.8-76.7S102.8 390.5 103 390c.2-.5 42-50.4 93.1-111s92.9-110.7 93.1-111.5c.2-.8.5-1.2.8-1.2z"
/>
<path
id="path923"
fill="#fff"
stroke="#fff"
strokeWidth="1.2357"
strokeLinecap="round"
strokeLinejoin="round"
d="M283.1 483.6c-5.8-2.1-12.8-8.1-15.7-13.7-3.6-6.9-3.3-17.7.7-26.3 3.1-6.4 3.1-6.6 1.1-8.1-1.1-.8-14.4-8.2-29.4-16.3-15-8.1-28.1-15.2-29-15.7-1.2-.7-3.2 0-6.8 2.3-11.7 7.4-23.9 6.6-33.5-2.3-6.9-6.4-8.9-10.9-8.9-20.1 0-8.9 1.8-13.5 7.5-19.2 7.7-7.7 18-10.3 27.9-7 5.4 1.8 5.5 1.8 8.9-.8 4-3 36.1-32.3 51.6-47l10.7-10.2-3.2-6.7c-6.5-13.5-3.2-28.5 8.2-37.5 6.2-4.9 10.8-6.4 19.7-6.4 20.8 0 35.3 21.8 27.5 41.3-2.1 5.4-2.1 5.5-.1 8.8 1.7 2.9 30.6 37.8 45.9 55.6 2.7 3.1 5.7 5.6 6.7 5.6s4.4-1 7.6-2.2c14.9-5.9 30.6.7 36.8 15.5 4 9.5.5 22.3-8 30-6 5.4-10.4 7.1-18.4 7.1-5.6 0-7.7-.6-13.6-3.8-4.4-2.4-7.8-3.6-9.2-3.2-2.4.6-39.3 25.9-47.5 32.5-5 4.1-5.4 5.6-2.8 11.7 2.5 6 2.2 15.4-.6 21.3-3.1 6.5-10.8 13-17.5 15-6.8 1.9-10.9 1.9-16.6-.2zm1.7-110.2v-57l-3.2-4.4c-1.8-2.4-3.5-4.4-3.8-4.4-1.3 0-65.9 58.7-65.9 59.9 0 .3 1 3.3 2.2 6.5 1.2 3.3 2.1 8 2 10.7-.1 2.7-.1 5.7-.1 6.7.1 2.3 21.7 16.1 54.1 34.8 8.9 5.2 12 6.5 13.1 5.6 1.3-1.1 1.6-12.2 1.6-58.4zm27.4 50.4c42.8-26.9 50.8-32.3 51.3-34.3.3-1.2.7-5.9.8-10.6l.3-8.4-21.8-25.9c-23.4-27.7-32-37.1-34-37.1-.7 0-4.2 2-7.8 4.4l-6.6 4.4.3 56.9c.3 51 .7 59.6 2.6 59.6.2.1 7-4 14.9-9z"
/>
</g>
</g>
</Icon>
)
}

View File

@ -0,0 +1,11 @@
import { IconProps, Icon } from '@chakra-ui/react'
export const FacebookLogo = (props: IconProps) => (
<Icon viewBox="0 0 14222 14222" {...props}>
<circle cx="7111" cy="7112" r="7111" fill="#1977f3" />
<path
d="M9879 9168l315-2056H8222V5778c0-562 275-1111 1159-1111h897V2917s-814-139-1592-139c-1624 0-2686 984-2686 2767v1567H4194v2056h1806v4969c362 57 733 86 1111 86s749-30 1111-86V9168z"
fill="#fff"
/>
</Icon>
)

View File

@ -0,0 +1,13 @@
import { IconProps, Icon } from '@chakra-ui/react'
export const GitlabLogo = (props: IconProps) => (
<Icon viewBox="0 0 256 236" {...props}>
<path d="M128.075 236.075l47.104-144.97H80.97l47.104 144.97z" fill="#E24329" />
<path d="M128.075 236.074L80.97 91.104H14.956l113.119 144.97z" fill="#FC6D26" />
<path d="M14.956 91.104L.642 135.16a9.752 9.752 0 0 0 3.542 10.903l123.891 90.012-113.12-144.97z" fill="#FCA326" />
<path d="M14.956 91.105H80.97L52.601 3.79c-1.46-4.493-7.816-4.492-9.275 0l-28.37 87.315z" fill="#E24329" />
<path d="M128.075 236.074l47.104-144.97h66.015l-113.12 144.97z" fill="#FC6D26" />
<path d="M241.194 91.104l14.314 44.056a9.752 9.752 0 0 1-3.543 10.903l-123.89 90.012 113.119-144.97z" fill="#FCA326" />
<path d="M241.194 91.105h-66.015l28.37-87.315c1.46-4.493 7.816-4.492 9.275 0l28.37 87.315z" fill="#E24329" />
</Icon>
)

View File

@ -0,0 +1,3 @@
export { AzureAdLogo } from './AzureAdLogo'
export { GitlabLogo } from './GitlabLogo'
export { FacebookLogo } from './FacebookLogo'

View File

@ -0,0 +1,15 @@
import { User } from 'db'
export const mockedUser: User = {
id: 'userId',
name: 'John Doe',
email: 'user@email.com',
company: null,
createdAt: new Date(),
emailVerified: null,
graphNavigation: 'TRACKPAD',
image: 'https://avatars.githubusercontent.com/u/16015833?v=4',
lastActivityAt: new Date(),
onboardingCategories: [],
updatedAt: new Date(),
}

View File

@ -0,0 +1,3 @@
export { SignInPage } from './components/SignInPage'
export { getAuthenticatedUser } from './api/getAuthenticatedUser'
export { mockedUser } from './constants'