2
0

Add user account page

This commit is contained in:
Baptiste Arnaud
2021-12-27 15:59:32 +01:00
parent 698867da5d
commit e10fe1a186
33 changed files with 911 additions and 129 deletions

View File

@ -1,20 +0,0 @@
import { useSession } from 'next-auth/react'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
const withAuth =
(WrappedComponent: (props: any) => JSX.Element) =>
(props: JSX.IntrinsicAttributes) => {
const router = useRouter()
const { status } = useSession()
useEffect(() => {
if (!router.isReady) return
if (status === 'loading') return
if (status === 'unauthenticated') router.replace('/signin')
}, [status, router])
return <WrappedComponent {...props} />
}
export default withAuth

View File

@ -0,0 +1,24 @@
import { Flex } from '@chakra-ui/react'
import { TypebotLogo } from 'assets/logos'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import React from 'react'
export const AccountHeader = () => (
<Flex w="full" borderBottomWidth="1px" justify="center">
<Flex
justify="space-between"
alignItems="center"
h="16"
maxW="1000px"
flex="1"
>
<NextChakraLink
className="w-24"
href="/typebots"
data-testid="authenticated"
>
<TypebotLogo w="30px" />
</NextChakraLink>
</Flex>
</Flex>
)

View File

@ -0,0 +1,27 @@
import { Stack, Heading, HStack, Button, Text } from '@chakra-ui/react'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import { useUser } from 'contexts/UserContext'
import React from 'react'
import { SubscriptionTag } from './SubscriptionTag'
export const BillingSection = () => {
const { user } = useUser()
return (
<Stack direction="row" spacing="10" justifyContent={'space-between'}>
<Heading as="h2" fontSize="xl">
Billing
</Heading>
<Stack spacing="6" w="400px">
<HStack>
<Text>Your subscription</Text>
<SubscriptionTag plan={user?.plan} />
</HStack>
{user?.stripeId && (
<Button as={NextChakraLink} href="test">
Billing portal
</Button>
)}
</Stack>
</Stack>
)
}

View File

@ -0,0 +1,116 @@
import {
Stack,
Heading,
HStack,
Avatar,
Button,
FormControl,
FormLabel,
Input,
Tooltip,
Flex,
Text,
} from '@chakra-ui/react'
import { UploadIcon } from 'assets/icons'
import { UploadButton } from 'components/shared/buttons/UploadButton'
import { useUser } from 'contexts/UserContext'
import React, { ChangeEvent, useState } from 'react'
import { uploadFile } from 'services/utils'
export const PersonalInfoForm = () => {
const {
user,
updateUser,
saveUser,
hasUnsavedChanges,
isSaving,
isOAuthProvider,
} = useUser()
const [reloadParam, setReloadParam] = useState('')
const [isUploading, setIsUploading] = useState(false)
const handleFileChange = async (file: File) => {
setIsUploading(true)
const { url } = await uploadFile(file, `${user?.id}/avatar`)
setReloadParam(Date.now().toString())
updateUser({ image: url })
setIsUploading(false)
}
const handleNameChange = (e: ChangeEvent<HTMLInputElement>) => {
updateUser({ name: e.target.value })
}
const handleEmailChange = (e: ChangeEvent<HTMLInputElement>) => {
updateUser({ email: e.target.value })
}
return (
<Stack direction="row" spacing="10" justifyContent={'space-between'}>
<Heading as="h2" fontSize="xl">
Personal info
</Heading>
<Stack spacing="6" w="400px">
<HStack spacing={6}>
<Avatar
size="lg"
src={user?.image ? `${user.image}?${reloadParam}` : undefined}
name={user?.name ?? undefined}
/>
<Stack>
<UploadButton
size="sm"
leftIcon={<UploadIcon />}
isLoading={isUploading}
onUploadChange={handleFileChange}
>
Change photo
</UploadButton>
<Text color="gray.500" fontSize="sm">
.jpg or.png, max 1MB
</Text>
</Stack>
</HStack>
<FormControl>
<FormLabel htmlFor="name">Name</FormLabel>
<Input
id="name"
value={user?.name ?? ''}
onChange={handleNameChange}
/>
</FormControl>
<Tooltip
label="Can't update the email because it is linked to an OAuth service"
placement="left"
hasArrow
isDisabled={!isOAuthProvider}
>
<FormControl>
<FormLabel
htmlFor="email"
color={isOAuthProvider ? 'gray.500' : 'current'}
>
Email address
</FormLabel>
<Input
id="email"
type="email"
isDisabled={isOAuthProvider}
value={user?.email ?? ''}
onChange={handleEmailChange}
/>
</FormControl>
</Tooltip>
{hasUnsavedChanges && (
<Flex justifyContent="flex-end">
<Button colorScheme="blue" onClick={saveUser} isLoading={isSaving}>
Save
</Button>
</Flex>
)}
</Stack>
</Stack>
)
}

View File

@ -0,0 +1,22 @@
import { Tag } from '@chakra-ui/react'
import { Plan } from 'db'
export const SubscriptionTag = ({ plan }: { plan?: Plan }) => {
switch (plan) {
case Plan.FREE: {
return <Tag>Free plan</Tag>
}
case Plan.LIFETIME: {
return <Tag colorScheme="yellow">Lifetime plan</Tag>
}
case Plan.OFFERED: {
return <Tag>Offered</Tag>
}
case Plan.PRO: {
return <Tag colorScheme="blue">Pro plan</Tag>
}
default: {
return <Tag>Free plan</Tag>
}
}
}

View File

@ -10,7 +10,7 @@ import { Block, StartBlock } from 'bot-engine'
import { useGraph } from 'contexts/GraphContext'
import { useDnd } from 'contexts/DndContext'
import { StepsList } from './StepsList'
import { isNotDefined } from 'services/utils'
import { isDefined } from 'services/utils'
import { useTypebot } from 'contexts/TypebotContext'
import { ContextMenu } from 'components/shared/ContextMenu'
import { BlockNodeContextMenu } from './BlockNodeContextMenu'
@ -34,7 +34,7 @@ export const BlockNode = ({ block }: { block: Block | StartBlock }) => {
useEffect(() => {
setIsConnecting(
connectingIds?.target?.blockId === block.id &&
isNotDefined(connectingIds.target?.stepId)
!isDefined(connectingIds.target?.stepId)
)
}, [block.id, connectingIds])

View File

@ -12,13 +12,13 @@ import {
Skeleton,
} from '@chakra-ui/react'
import { TypebotLogo } from 'assets/logos'
import { useUser } from 'services/user'
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
import { LogOutIcon, SettingsIcon } from 'assets/icons'
import { signOut } from 'next-auth/react'
import { useUser } from 'contexts/UserContext'
export const DashboardHeader = () => {
const user = useUser()
const { user } = useUser()
const handleLogOut = () => {
signOut()

View File

@ -0,0 +1,34 @@
import { Button, ButtonProps, chakra } from '@chakra-ui/react'
import React, { ChangeEvent } from 'react'
type UploadButtonProps = { onUploadChange: (file: File) => void } & ButtonProps
export const UploadButton = ({
onUploadChange,
...props
}: UploadButtonProps) => {
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
if (!e.target?.files) return
onUploadChange(e.target.files[0])
}
return (
<>
<chakra.input
type="file"
id="file-input"
display="none"
onChange={handleInputChange}
accept=".jpg, .jpeg, .png"
/>
<Button
as="label"
size="sm"
htmlFor="file-input"
cursor="pointer"
{...props}
>
{props.children}
</Button>
</>
)
}