Add user account page
This commit is contained in:
@ -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
|
24
apps/builder/components/account/AccountHeader.tsx
Normal file
24
apps/builder/components/account/AccountHeader.tsx
Normal 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>
|
||||
)
|
27
apps/builder/components/account/BillingSection.tsx
Normal file
27
apps/builder/components/account/BillingSection.tsx
Normal 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>
|
||||
)
|
||||
}
|
116
apps/builder/components/account/PersonalInfoForm.tsx
Normal file
116
apps/builder/components/account/PersonalInfoForm.tsx
Normal 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>
|
||||
)
|
||||
}
|
22
apps/builder/components/account/SubscriptionTag.tsx
Normal file
22
apps/builder/components/account/SubscriptionTag.tsx
Normal 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>
|
||||
}
|
||||
}
|
||||
}
|
@ -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])
|
||||
|
||||
|
@ -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()
|
||||
|
34
apps/builder/components/shared/buttons/UploadButton.tsx
Normal file
34
apps/builder/components/shared/buttons/UploadButton.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user