Add user account page
This commit is contained in:
@@ -23,6 +23,11 @@ GOOGLE_CLIENT_SECRET=
|
|||||||
FACEBOOK_CLIENT_ID=
|
FACEBOOK_CLIENT_ID=
|
||||||
FACEBOOK_CLIENT_SECRET=
|
FACEBOOK_CLIENT_SECRET=
|
||||||
|
|
||||||
|
# (Optional) Subscription Payment
|
||||||
|
NEXT_PUBLIC_STRIPE_PUBLIC_KEY=
|
||||||
|
STRIPE_SECRET_KEY=
|
||||||
|
STRIPE_WEBHOOK_SECRET=
|
||||||
|
|
||||||
# Used for uploading images, videos, etc...
|
# Used for uploading images, videos, etc...
|
||||||
S3_UPLOAD_KEY=
|
S3_UPLOAD_KEY=
|
||||||
S3_UPLOAD_SECRET=
|
S3_UPLOAD_SECRET=
|
||||||
|
|||||||
@@ -191,3 +191,12 @@ export const EditIcon = (props: IconProps) => (
|
|||||||
<path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
|
<path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"></path>
|
||||||
</Icon>
|
</Icon>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const UploadIcon = (props: IconProps) => (
|
||||||
|
<Icon viewBox="0 0 24 24" {...featherIconsBaseProps} {...props}>
|
||||||
|
<polyline points="16 16 12 12 8 16"></polyline>
|
||||||
|
<line x1="12" y1="12" x2="12" y2="21"></line>
|
||||||
|
<path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"></path>
|
||||||
|
<polyline points="16 16 12 12 8 16"></polyline>
|
||||||
|
</Icon>
|
||||||
|
)
|
||||||
|
|||||||
@@ -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 { useGraph } from 'contexts/GraphContext'
|
||||||
import { useDnd } from 'contexts/DndContext'
|
import { useDnd } from 'contexts/DndContext'
|
||||||
import { StepsList } from './StepsList'
|
import { StepsList } from './StepsList'
|
||||||
import { isNotDefined } from 'services/utils'
|
import { isDefined } from 'services/utils'
|
||||||
import { useTypebot } from 'contexts/TypebotContext'
|
import { useTypebot } from 'contexts/TypebotContext'
|
||||||
import { ContextMenu } from 'components/shared/ContextMenu'
|
import { ContextMenu } from 'components/shared/ContextMenu'
|
||||||
import { BlockNodeContextMenu } from './BlockNodeContextMenu'
|
import { BlockNodeContextMenu } from './BlockNodeContextMenu'
|
||||||
@@ -34,7 +34,7 @@ export const BlockNode = ({ block }: { block: Block | StartBlock }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsConnecting(
|
setIsConnecting(
|
||||||
connectingIds?.target?.blockId === block.id &&
|
connectingIds?.target?.blockId === block.id &&
|
||||||
isNotDefined(connectingIds.target?.stepId)
|
!isDefined(connectingIds.target?.stepId)
|
||||||
)
|
)
|
||||||
}, [block.id, connectingIds])
|
}, [block.id, connectingIds])
|
||||||
|
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ import {
|
|||||||
Skeleton,
|
Skeleton,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { TypebotLogo } from 'assets/logos'
|
import { TypebotLogo } from 'assets/logos'
|
||||||
import { useUser } from 'services/user'
|
|
||||||
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
|
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
|
||||||
import { LogOutIcon, SettingsIcon } from 'assets/icons'
|
import { LogOutIcon, SettingsIcon } from 'assets/icons'
|
||||||
import { signOut } from 'next-auth/react'
|
import { signOut } from 'next-auth/react'
|
||||||
|
import { useUser } from 'contexts/UserContext'
|
||||||
|
|
||||||
export const DashboardHeader = () => {
|
export const DashboardHeader = () => {
|
||||||
const user = useUser()
|
const { user } = useUser()
|
||||||
|
|
||||||
const handleLogOut = () => {
|
const handleLogOut = () => {
|
||||||
signOut()
|
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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
100
apps/builder/contexts/UserContext.tsx
Normal file
100
apps/builder/contexts/UserContext.tsx
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { User } from 'db'
|
||||||
|
import { useSession } from 'next-auth/react'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
ReactNode,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
|
import { isDefined } from 'services/utils'
|
||||||
|
import { updateUser as updateUserInDb } from 'services/user'
|
||||||
|
import { useToast } from '@chakra-ui/react'
|
||||||
|
import { deepEqual } from 'fast-equals'
|
||||||
|
|
||||||
|
const userContext = createContext<{
|
||||||
|
user?: User
|
||||||
|
isLoading: boolean
|
||||||
|
isSaving: boolean
|
||||||
|
hasUnsavedChanges: boolean
|
||||||
|
isOAuthProvider: boolean
|
||||||
|
updateUser: (newUser: Partial<User>) => void
|
||||||
|
saveUser: () => void
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
//@ts-ignore
|
||||||
|
}>({})
|
||||||
|
|
||||||
|
export const UserContext = ({ children }: { children: ReactNode }) => {
|
||||||
|
const router = useRouter()
|
||||||
|
const { data: session, status } = useSession()
|
||||||
|
|
||||||
|
const [user, setUser] = useState<User>()
|
||||||
|
const [isSaving, setIsSaving] = useState(false)
|
||||||
|
const isOAuthProvider = useMemo(
|
||||||
|
() => (session?.providerType as boolean | undefined) ?? false,
|
||||||
|
[session?.providerType]
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasUnsavedChanges = useMemo(
|
||||||
|
() => !deepEqual(session?.user, user),
|
||||||
|
[session?.user, user]
|
||||||
|
)
|
||||||
|
|
||||||
|
const toast = useToast({
|
||||||
|
position: 'top-right',
|
||||||
|
status: 'error',
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isDefined(user) || !isDefined(session)) return
|
||||||
|
|
||||||
|
setUser(session.user as User)
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [session])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!router.isReady) return
|
||||||
|
if (status === 'loading') return
|
||||||
|
if (status === 'unauthenticated') router.replace('/signin')
|
||||||
|
}, [status, router])
|
||||||
|
|
||||||
|
const updateUser = (newUser: Partial<User>) => {
|
||||||
|
if (!isDefined(user)) return
|
||||||
|
setUser({ ...user, ...newUser })
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveUser = async () => {
|
||||||
|
if (!isDefined(user)) return
|
||||||
|
setIsSaving(true)
|
||||||
|
const { error } = await updateUserInDb(user.id, user)
|
||||||
|
if (error) toast({ title: error.name, description: error.message })
|
||||||
|
await fetch('/api/auth/session?update')
|
||||||
|
reloadSession()
|
||||||
|
setIsSaving(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<userContext.Provider
|
||||||
|
value={{
|
||||||
|
user,
|
||||||
|
updateUser,
|
||||||
|
saveUser,
|
||||||
|
isSaving,
|
||||||
|
isLoading: status === 'loading',
|
||||||
|
hasUnsavedChanges,
|
||||||
|
isOAuthProvider,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</userContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const reloadSession = () => {
|
||||||
|
const event = new Event('visibilitychange')
|
||||||
|
document.dispatchEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useUser = () => useContext(userContext)
|
||||||
34
apps/builder/layouts/account/AccountContent.tsx
Normal file
34
apps/builder/layouts/account/AccountContent.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { Flex, Stack, Heading, Divider, Button } from '@chakra-ui/react'
|
||||||
|
import { ChevronLeftIcon } from 'assets/icons'
|
||||||
|
import { NextChakraLink } from 'components/nextChakra/NextChakraLink'
|
||||||
|
import React from 'react'
|
||||||
|
import { PersonalInfoForm } from 'components/account/PersonalInfoForm'
|
||||||
|
import { BillingSection } from 'components/account/BillingSection'
|
||||||
|
|
||||||
|
export const AccountContent = () => {
|
||||||
|
return (
|
||||||
|
<Flex h="full" w="full" justifyContent="center" align="flex-start">
|
||||||
|
<Stack maxW="600px" w="full" pt="4" spacing={10}>
|
||||||
|
<Flex>
|
||||||
|
<Button
|
||||||
|
as={NextChakraLink}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
leftIcon={<ChevronLeftIcon />}
|
||||||
|
href="/typebots"
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
|
<Heading as="h1" fontSize="3xl">
|
||||||
|
Account Settings
|
||||||
|
</Heading>
|
||||||
|
<Divider />
|
||||||
|
<PersonalInfoForm />
|
||||||
|
<Divider />
|
||||||
|
<BillingSection />
|
||||||
|
</Stack>
|
||||||
|
</Flex>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
"@udecode/plate-link": "^9.0.0",
|
"@udecode/plate-link": "^9.0.0",
|
||||||
"@udecode/plate-ui-link": "^9.0.0",
|
"@udecode/plate-ui-link": "^9.0.0",
|
||||||
"@udecode/plate-ui-toolbar": "^9.0.0",
|
"@udecode/plate-ui-toolbar": "^9.0.0",
|
||||||
|
"aws-sdk": "^2.1048.0",
|
||||||
"bot-engine": "*",
|
"bot-engine": "*",
|
||||||
"db": "*",
|
"db": "*",
|
||||||
"fast-equals": "^2.0.4",
|
"fast-equals": "^2.0.4",
|
||||||
@@ -31,6 +32,7 @@
|
|||||||
"framer-motion": "^4",
|
"framer-motion": "^4",
|
||||||
"htmlparser2": "^7.2.0",
|
"htmlparser2": "^7.2.0",
|
||||||
"kbar": "^0.1.0-beta.24",
|
"kbar": "^0.1.0-beta.24",
|
||||||
|
"micro-cors": "^0.1.1",
|
||||||
"next": "^12.0.7",
|
"next": "^12.0.7",
|
||||||
"next-auth": "beta",
|
"next-auth": "beta",
|
||||||
"nodemailer": "^6.7.2",
|
"nodemailer": "^6.7.2",
|
||||||
@@ -44,6 +46,7 @@
|
|||||||
"slate-history": "^0.66.0",
|
"slate-history": "^0.66.0",
|
||||||
"slate-hyperscript": "^0.67.0",
|
"slate-hyperscript": "^0.67.0",
|
||||||
"slate-react": "^0.72.1",
|
"slate-react": "^0.72.1",
|
||||||
|
"stripe": "^8.195.0",
|
||||||
"styled-components": "^5.3.3",
|
"styled-components": "^5.3.3",
|
||||||
"svg-round-corners": "^0.3.0",
|
"svg-round-corners": "^0.3.0",
|
||||||
"swr": "^1.1.1",
|
"swr": "^1.1.1",
|
||||||
@@ -51,6 +54,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@testing-library/cypress": "^8.0.2",
|
"@testing-library/cypress": "^8.0.2",
|
||||||
|
"@types/micro-cors": "^0.1.2",
|
||||||
"@types/node": "^16.11.9",
|
"@types/node": "^16.11.9",
|
||||||
"@types/nprogress": "^0.2.0",
|
"@types/nprogress": "^0.2.0",
|
||||||
"@types/react": "^17.0.37",
|
"@types/react": "^17.0.37",
|
||||||
|
|||||||
18
apps/builder/pages/account.tsx
Normal file
18
apps/builder/pages/account.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Stack } from '@chakra-ui/react'
|
||||||
|
import { AccountHeader } from 'components/account/AccountHeader'
|
||||||
|
import { Seo } from 'components/Seo'
|
||||||
|
import { UserContext } from 'contexts/UserContext'
|
||||||
|
import { AccountContent } from 'layouts/account/AccountContent'
|
||||||
|
|
||||||
|
const AccountSubscriptionPage = () => {
|
||||||
|
return (
|
||||||
|
<UserContext>
|
||||||
|
<Seo title="My account" />{' '}
|
||||||
|
<Stack>
|
||||||
|
<AccountHeader />
|
||||||
|
<AccountContent />
|
||||||
|
</Stack>
|
||||||
|
</UserContext>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default AccountSubscriptionPage
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import NextAuth from 'next-auth'
|
import NextAuth, { NextAuthOptions } from 'next-auth'
|
||||||
import { PrismaAdapter } from '@next-auth/prisma-adapter'
|
import { PrismaAdapter } from '@next-auth/prisma-adapter'
|
||||||
import EmailProvider from 'next-auth/providers/email'
|
import EmailProvider from 'next-auth/providers/email'
|
||||||
import GitHubProvider from 'next-auth/providers/github'
|
import GitHubProvider from 'next-auth/providers/github'
|
||||||
@@ -8,6 +8,7 @@ import CredentialsProvider from 'next-auth/providers/credentials'
|
|||||||
import prisma from 'libs/prisma'
|
import prisma from 'libs/prisma'
|
||||||
import { Provider } from 'next-auth/providers'
|
import { Provider } from 'next-auth/providers'
|
||||||
import { User } from 'db'
|
import { User } from 'db'
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
const providers: Provider[] = [
|
const providers: Provider[] = [
|
||||||
EmailProvider({
|
EmailProvider({
|
||||||
@@ -67,7 +68,7 @@ if (process.env.NODE_ENV !== 'production')
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
export default NextAuth({
|
const createOptions = (req: NextApiRequest): NextAuthOptions => ({
|
||||||
adapter: PrismaAdapter(prisma),
|
adapter: PrismaAdapter(prisma),
|
||||||
secret: process.env.SECRET,
|
secret: process.env.SECRET,
|
||||||
providers,
|
providers,
|
||||||
@@ -75,13 +76,25 @@ export default NextAuth({
|
|||||||
strategy: process.env.NODE_ENV === 'production' ? 'database' : 'jwt',
|
strategy: process.env.NODE_ENV === 'production' ? 'database' : 'jwt',
|
||||||
},
|
},
|
||||||
callbacks: {
|
callbacks: {
|
||||||
jwt: async ({ token, user }) => {
|
jwt: async ({ token, user, account }) => {
|
||||||
user && (token.user = user)
|
if (req.url === '/api/auth/session?update' && token.user) {
|
||||||
|
token.user = await prisma.user.findUnique({
|
||||||
|
where: { id: (token.user as User).id },
|
||||||
|
})
|
||||||
|
} else if (user) {
|
||||||
|
token.user = user
|
||||||
|
}
|
||||||
|
account?.type && (token.providerType = account?.type)
|
||||||
return token
|
return token
|
||||||
},
|
},
|
||||||
session: async ({ session, token, user }) => {
|
session: async ({ session, token, user }) => {
|
||||||
token?.user ? (session.user = token.user as User) : (session.user = user)
|
token?.user ? (session.user = token.user as User) : (session.user = user)
|
||||||
return session
|
return { ...session, providerType: token.providerType }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
NextAuth(req, res, createOptions(req))
|
||||||
|
}
|
||||||
|
export default handler
|
||||||
|
|||||||
46
apps/builder/pages/api/storage/upload-url.ts
Normal file
46
apps/builder/pages/api/storage/upload-url.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import aws from 'aws-sdk'
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import { getSession } from 'next-auth/react'
|
||||||
|
import { methodNotAllowed } from 'services/api/utils'
|
||||||
|
|
||||||
|
const maxUploadFileSize = 10485760 // 10 MB
|
||||||
|
const handler = async (
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
): Promise<void> => {
|
||||||
|
try {
|
||||||
|
res.setHeader('Access-Control-Allow-Origin', '*')
|
||||||
|
if (req.method === 'GET') {
|
||||||
|
const session = await getSession({ req })
|
||||||
|
if (!session) {
|
||||||
|
res.status(401)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
aws.config.update({
|
||||||
|
accessKeyId: process.env.S3_UPLOAD_KEY,
|
||||||
|
secretAccessKey: process.env.S3_UPLOAD_SECRET,
|
||||||
|
region: process.env.S3_UPLOAD_REGION,
|
||||||
|
signatureVersion: 'v4',
|
||||||
|
})
|
||||||
|
|
||||||
|
const s3 = new aws.S3()
|
||||||
|
const post = s3.createPresignedPost({
|
||||||
|
Bucket: process.env.S3_UPLOAD_BUCKET,
|
||||||
|
Fields: {
|
||||||
|
ACL: 'public-read',
|
||||||
|
key: req.query.key,
|
||||||
|
'Content-Type': req.query.fileType,
|
||||||
|
},
|
||||||
|
Expires: 120, // seconds
|
||||||
|
Conditions: [['content-length-range', 0, maxUploadFileSize]],
|
||||||
|
})
|
||||||
|
|
||||||
|
return res.status(200).json(post)
|
||||||
|
}
|
||||||
|
return methodNotAllowed(res)
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default handler
|
||||||
35
apps/builder/pages/api/stripe/checkout.ts
Normal file
35
apps/builder/pages/api/stripe/checkout.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import { methodNotAllowed } from 'services/api/utils'
|
||||||
|
import Stripe from 'stripe'
|
||||||
|
|
||||||
|
const usdPriceIdTest = 'price_1Jc4TQKexUFvKTWyGvsH4Ff5'
|
||||||
|
const createCheckoutSession = async (
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse
|
||||||
|
) => {
|
||||||
|
if (req.method === 'POST') {
|
||||||
|
if (!process.env.STRIPE_SECRET_KEY)
|
||||||
|
throw Error('STRIPE_SECRET_KEY var is missing')
|
||||||
|
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
|
||||||
|
apiVersion: '2020-08-27',
|
||||||
|
})
|
||||||
|
const { email } = req.body
|
||||||
|
const session = await stripe.checkout.sessions.create({
|
||||||
|
success_url: `${req.headers.origin}/typebots?stripe=success`,
|
||||||
|
cancel_url: `${req.headers.origin}/typebots?stripe=cancel`,
|
||||||
|
automatic_tax: { enabled: true },
|
||||||
|
allow_promotion_codes: true,
|
||||||
|
customer_email: email,
|
||||||
|
line_items: [
|
||||||
|
{
|
||||||
|
price: usdPriceIdTest,
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
res.status(201).json(session)
|
||||||
|
}
|
||||||
|
return methodNotAllowed(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createCheckoutSession
|
||||||
73
apps/builder/pages/api/stripe/webhook.ts
Normal file
73
apps/builder/pages/api/stripe/webhook.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import { methodNotAllowed } from 'services/api/utils'
|
||||||
|
import Stripe from 'stripe'
|
||||||
|
import Cors from 'micro-cors'
|
||||||
|
import { buffer } from 'micro'
|
||||||
|
import prisma from 'libs/prisma'
|
||||||
|
import { Plan } from 'db'
|
||||||
|
|
||||||
|
if (!process.env.STRIPE_SECRET_KEY || !process.env.STRIPE_WEBHOOK_SECRET)
|
||||||
|
throw new Error('STRIPE_SECRET_KEY or STRIPE_WEBHOOK_SECRET missing')
|
||||||
|
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
|
||||||
|
apiVersion: '2020-08-27',
|
||||||
|
})
|
||||||
|
|
||||||
|
const cors = Cors({
|
||||||
|
allowMethods: ['POST', 'HEAD'],
|
||||||
|
})
|
||||||
|
|
||||||
|
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET as string
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
api: {
|
||||||
|
bodyParser: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
if (req.method === 'POST') {
|
||||||
|
const buf = await buffer(req)
|
||||||
|
const sig = req.headers['stripe-signature']
|
||||||
|
|
||||||
|
if (!sig) return res.status(400).send(`stripe-signature is missing`)
|
||||||
|
try {
|
||||||
|
const event = stripe.webhooks.constructEvent(
|
||||||
|
buf.toString(),
|
||||||
|
sig.toString(),
|
||||||
|
webhookSecret
|
||||||
|
)
|
||||||
|
switch (event.type) {
|
||||||
|
case 'checkout.session.completed': {
|
||||||
|
const session = event.data.object as Stripe.Checkout.Session
|
||||||
|
const { customer_email } = session
|
||||||
|
if (!customer_email)
|
||||||
|
return res.status(500).send(`customer_email not found`)
|
||||||
|
await prisma.user.update({
|
||||||
|
where: { email: customer_email },
|
||||||
|
data: { plan: Plan.PRO, stripeId: session.customer as string },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
case 'customer.subscription.deleted': {
|
||||||
|
const subscription = event.data.object as Stripe.Subscription
|
||||||
|
await prisma.user.update({
|
||||||
|
where: {
|
||||||
|
stripeId: subscription.customer as string,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
plan: Plan.FREE,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
console.error(err)
|
||||||
|
return res.status(400).send(`Webhook Error: ${err.message}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return methodNotAllowed(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export default cors(webhookHandler as any)
|
||||||
24
apps/builder/pages/api/users/[id].ts
Normal file
24
apps/builder/pages/api/users/[id].ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import prisma from 'libs/prisma'
|
||||||
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import { getSession } from 'next-auth/react'
|
||||||
|
import { methodNotAllowed } from 'services/api/utils'
|
||||||
|
|
||||||
|
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
const session = await getSession({ req })
|
||||||
|
|
||||||
|
if (!session?.user)
|
||||||
|
return res.status(401).json({ message: 'Not authenticated' })
|
||||||
|
|
||||||
|
const id = req.query.id.toString()
|
||||||
|
if (req.method === 'PUT') {
|
||||||
|
const data = JSON.parse(req.body)
|
||||||
|
const typebots = await prisma.user.update({
|
||||||
|
where: { id },
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
return res.send({ typebots })
|
||||||
|
}
|
||||||
|
return methodNotAllowed(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default handler
|
||||||
@@ -1,18 +1,20 @@
|
|||||||
import withAuth from 'components/HOC/withUser'
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Stack } from '@chakra-ui/layout'
|
import { Stack } from '@chakra-ui/layout'
|
||||||
import { DashboardHeader } from 'components/dashboard/DashboardHeader'
|
import { DashboardHeader } from 'components/dashboard/DashboardHeader'
|
||||||
import { Seo } from 'components/Seo'
|
import { Seo } from 'components/Seo'
|
||||||
import { FolderContent } from 'components/dashboard/FolderContent'
|
import { FolderContent } from 'components/dashboard/FolderContent'
|
||||||
|
import { UserContext } from 'contexts/UserContext'
|
||||||
|
|
||||||
const DashboardPage = () => {
|
const DashboardPage = () => {
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<UserContext>
|
||||||
<Seo title="My typebots" />
|
<Seo title="My typebots" />
|
||||||
|
<Stack>
|
||||||
<DashboardHeader />
|
<DashboardHeader />
|
||||||
<FolderContent folder={null} />
|
<FolderContent folder={null} />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</UserContext>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withAuth(DashboardPage)
|
export default DashboardPage
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Flex } from '@chakra-ui/layout'
|
import { Flex } from '@chakra-ui/layout'
|
||||||
import { Board } from 'components/board/Board'
|
import { Board } from 'components/board/Board'
|
||||||
import withAuth from 'components/HOC/withUser'
|
|
||||||
import { Seo } from 'components/Seo'
|
import { Seo } from 'components/Seo'
|
||||||
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
||||||
import { EditorContext } from 'contexts/EditorContext'
|
import { EditorContext } from 'contexts/EditorContext'
|
||||||
@@ -11,10 +10,12 @@ import { KBarProvider } from 'kbar'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { actions } from 'libs/kbar'
|
import { actions } from 'libs/kbar'
|
||||||
import { KBar } from 'components/shared/KBar'
|
import { KBar } from 'components/shared/KBar'
|
||||||
|
import { UserContext } from 'contexts/UserContext'
|
||||||
|
|
||||||
const TypebotEditPage = () => {
|
const TypebotEditPage = () => {
|
||||||
const { query } = useRouter()
|
const { query } = useRouter()
|
||||||
return (
|
return (
|
||||||
|
<UserContext>
|
||||||
<TypebotContext typebotId={query.id?.toString()}>
|
<TypebotContext typebotId={query.id?.toString()}>
|
||||||
<Seo title="Editor" />
|
<Seo title="Editor" />
|
||||||
<EditorContext>
|
<EditorContext>
|
||||||
@@ -29,7 +30,8 @@ const TypebotEditPage = () => {
|
|||||||
</KBarProvider>
|
</KBarProvider>
|
||||||
</EditorContext>
|
</EditorContext>
|
||||||
</TypebotContext>
|
</TypebotContext>
|
||||||
|
</UserContext>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withAuth(TypebotEditPage)
|
export default TypebotEditPage
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { Flex } from '@chakra-ui/layout'
|
import { Flex } from '@chakra-ui/layout'
|
||||||
import withAuth from 'components/HOC/withUser'
|
|
||||||
import { ResultsContent } from 'layouts/results/ResultsContent'
|
import { ResultsContent } from 'layouts/results/ResultsContent'
|
||||||
import { Seo } from 'components/Seo'
|
import { Seo } from 'components/Seo'
|
||||||
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
||||||
import { TypebotContext } from 'contexts/TypebotContext'
|
import { TypebotContext } from 'contexts/TypebotContext'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { UserContext } from 'contexts/UserContext'
|
||||||
|
|
||||||
const ResultsPage = () => {
|
const ResultsPage = () => {
|
||||||
const { query } = useRouter()
|
const { query } = useRouter()
|
||||||
return (
|
return (
|
||||||
|
<UserContext>
|
||||||
<TypebotContext typebotId={query.id?.toString()}>
|
<TypebotContext typebotId={query.id?.toString()}>
|
||||||
<Seo title="Share" />
|
<Seo title="Share" />
|
||||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||||
@@ -17,7 +18,8 @@ const ResultsPage = () => {
|
|||||||
<ResultsContent />
|
<ResultsContent />
|
||||||
</Flex>
|
</Flex>
|
||||||
</TypebotContext>
|
</TypebotContext>
|
||||||
|
</UserContext>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withAuth(ResultsPage)
|
export default ResultsPage
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { Flex } from '@chakra-ui/layout'
|
import { Flex } from '@chakra-ui/layout'
|
||||||
import withAuth from 'components/HOC/withUser'
|
|
||||||
import { ResultsContent } from 'layouts/results/ResultsContent'
|
import { ResultsContent } from 'layouts/results/ResultsContent'
|
||||||
import { Seo } from 'components/Seo'
|
import { Seo } from 'components/Seo'
|
||||||
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
||||||
import { TypebotContext } from 'contexts/TypebotContext'
|
import { TypebotContext } from 'contexts/TypebotContext'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import { UserContext } from 'contexts/UserContext'
|
||||||
|
|
||||||
const AnalyticsPage = () => {
|
const AnalyticsPage = () => {
|
||||||
const { query } = useRouter()
|
const { query } = useRouter()
|
||||||
return (
|
return (
|
||||||
|
<UserContext>
|
||||||
<TypebotContext typebotId={query.id?.toString()}>
|
<TypebotContext typebotId={query.id?.toString()}>
|
||||||
<Seo title="Analytics" />
|
<Seo title="Analytics" />
|
||||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||||
@@ -17,7 +18,8 @@ const AnalyticsPage = () => {
|
|||||||
<ResultsContent />
|
<ResultsContent />
|
||||||
</Flex>
|
</Flex>
|
||||||
</TypebotContext>
|
</TypebotContext>
|
||||||
|
</UserContext>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withAuth(AnalyticsPage)
|
export default AnalyticsPage
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { Flex } from '@chakra-ui/layout'
|
import { Flex } from '@chakra-ui/layout'
|
||||||
import withAuth from 'components/HOC/withUser'
|
|
||||||
import { Seo } from 'components/Seo'
|
import { Seo } from 'components/Seo'
|
||||||
import { SettingsContent } from 'components/settings/SettingsContent'
|
import { SettingsContent } from 'components/settings/SettingsContent'
|
||||||
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
||||||
import { TypebotContext } from 'contexts/TypebotContext'
|
import { TypebotContext } from 'contexts/TypebotContext'
|
||||||
|
import { UserContext } from 'contexts/UserContext'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
const SettingsPage = () => {
|
const SettingsPage = () => {
|
||||||
const { query } = useRouter()
|
const { query } = useRouter()
|
||||||
return (
|
return (
|
||||||
|
<UserContext>
|
||||||
<TypebotContext typebotId={query.id?.toString()}>
|
<TypebotContext typebotId={query.id?.toString()}>
|
||||||
<Seo title="Settings" />
|
<Seo title="Settings" />
|
||||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||||
@@ -17,7 +18,8 @@ const SettingsPage = () => {
|
|||||||
<SettingsContent />
|
<SettingsContent />
|
||||||
</Flex>
|
</Flex>
|
||||||
</TypebotContext>
|
</TypebotContext>
|
||||||
|
</UserContext>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withAuth(SettingsPage)
|
export default SettingsPage
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { Flex } from '@chakra-ui/layout'
|
import { Flex } from '@chakra-ui/layout'
|
||||||
import withAuth from 'components/HOC/withUser'
|
|
||||||
import { Seo } from 'components/Seo'
|
import { Seo } from 'components/Seo'
|
||||||
import { ShareContent } from 'components/share/ShareContent'
|
import { ShareContent } from 'components/share/ShareContent'
|
||||||
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
||||||
import { TypebotContext } from 'contexts/TypebotContext'
|
import { TypebotContext } from 'contexts/TypebotContext'
|
||||||
|
import { UserContext } from 'contexts/UserContext'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
const SharePage = () => {
|
const SharePage = () => {
|
||||||
const { query } = useRouter()
|
const { query } = useRouter()
|
||||||
return (
|
return (
|
||||||
|
<UserContext>
|
||||||
<TypebotContext typebotId={query.id?.toString()}>
|
<TypebotContext typebotId={query.id?.toString()}>
|
||||||
<Seo title="Share" />
|
<Seo title="Share" />
|
||||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||||
@@ -17,7 +18,8 @@ const SharePage = () => {
|
|||||||
<ShareContent />
|
<ShareContent />
|
||||||
</Flex>
|
</Flex>
|
||||||
</TypebotContext>
|
</TypebotContext>
|
||||||
|
</UserContext>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withAuth(SharePage)
|
export default SharePage
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { Flex } from '@chakra-ui/layout'
|
import { Flex } from '@chakra-ui/layout'
|
||||||
import withAuth from 'components/HOC/withUser'
|
|
||||||
import { Seo } from 'components/Seo'
|
import { Seo } from 'components/Seo'
|
||||||
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
||||||
import { ThemeContent } from 'components/theme/ThemeContent'
|
import { ThemeContent } from 'components/theme/ThemeContent'
|
||||||
import { TypebotContext } from 'contexts/TypebotContext'
|
import { TypebotContext } from 'contexts/TypebotContext'
|
||||||
|
import { UserContext } from 'contexts/UserContext'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
const ThemePage = () => {
|
const ThemePage = () => {
|
||||||
const { query } = useRouter()
|
const { query } = useRouter()
|
||||||
return (
|
return (
|
||||||
|
<UserContext>
|
||||||
<TypebotContext typebotId={query.id?.toString()}>
|
<TypebotContext typebotId={query.id?.toString()}>
|
||||||
<Seo title="Theme" />
|
<Seo title="Theme" />
|
||||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||||
@@ -17,7 +18,8 @@ const ThemePage = () => {
|
|||||||
<ThemeContent />
|
<ThemeContent />
|
||||||
</Flex>
|
</Flex>
|
||||||
</TypebotContext>
|
</TypebotContext>
|
||||||
|
</UserContext>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withAuth(ThemePage)
|
export default ThemePage
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { Button, Stack, useToast } from '@chakra-ui/react'
|
import { Button, Stack, useToast } from '@chakra-ui/react'
|
||||||
import { useUser } from 'services/user'
|
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { Seo } from 'components/Seo'
|
import { Seo } from 'components/Seo'
|
||||||
import { DashboardHeader } from 'components/dashboard/DashboardHeader'
|
import { DashboardHeader } from 'components/dashboard/DashboardHeader'
|
||||||
import { createTypebot } from 'services/typebots'
|
import { createTypebot } from 'services/typebots'
|
||||||
import withAuth from 'components/HOC/withUser'
|
import { UserContext, useUser } from 'contexts/UserContext'
|
||||||
|
|
||||||
const TemplatesPage = () => {
|
const TemplatesPage = () => {
|
||||||
const user = useUser()
|
const { user } = useUser()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
@@ -31,14 +30,20 @@ const TemplatesPage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<UserContext>
|
||||||
<Seo title="Templates" />
|
<Seo title="Templates" />
|
||||||
|
<Stack>
|
||||||
<DashboardHeader />
|
<DashboardHeader />
|
||||||
<Button ml={4} onClick={() => handleCreateSubmit()} isLoading={isLoading}>
|
<Button
|
||||||
|
ml={4}
|
||||||
|
onClick={() => handleCreateSubmit()}
|
||||||
|
isLoading={isLoading}
|
||||||
|
>
|
||||||
Start from scratch
|
Start from scratch
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</UserContext>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withAuth(TemplatesPage)
|
export default TemplatesPage
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import withAuth from 'components/HOC/withUser'
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Flex, Stack } from '@chakra-ui/layout'
|
import { Flex, Stack } from '@chakra-ui/layout'
|
||||||
import { DashboardHeader } from 'components/dashboard/DashboardHeader'
|
import { DashboardHeader } from 'components/dashboard/DashboardHeader'
|
||||||
@@ -7,6 +6,7 @@ import { FolderContent } from 'components/dashboard/FolderContent'
|
|||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useFolderContent } from 'services/folders'
|
import { useFolderContent } from 'services/folders'
|
||||||
import { Spinner, useToast } from '@chakra-ui/react'
|
import { Spinner, useToast } from '@chakra-ui/react'
|
||||||
|
import { UserContext } from 'contexts/UserContext'
|
||||||
|
|
||||||
const FolderPage = () => {
|
const FolderPage = () => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -27,8 +27,9 @@ const FolderPage = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<UserContext>
|
||||||
<Seo title="My typebots" />
|
<Seo title="My typebots" />
|
||||||
|
<Stack>
|
||||||
<DashboardHeader />
|
<DashboardHeader />
|
||||||
{!folder ? (
|
{!folder ? (
|
||||||
<Flex flex="1">
|
<Flex flex="1">
|
||||||
@@ -38,7 +39,8 @@ const FolderPage = () => {
|
|||||||
<FolderContent folder={folder} />
|
<FolderContent folder={folder} />
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
</UserContext>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withAuth(FolderPage)
|
export default FolderPage
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { User } from 'db'
|
import { User } from 'db'
|
||||||
import { useSession } from 'next-auth/react'
|
import { sendRequest } from './utils'
|
||||||
|
|
||||||
export const useUser = (): User | undefined => {
|
export const updateUser = async (id: string, user: User) =>
|
||||||
const { data } = useSession()
|
sendRequest({
|
||||||
return data?.user as User | undefined
|
url: `/api/users/${id}`,
|
||||||
}
|
method: 'PUT',
|
||||||
|
body: user,
|
||||||
|
})
|
||||||
|
|||||||
@@ -43,10 +43,6 @@ export const isDefined = <T>(value: T | undefined | null): value is T => {
|
|||||||
return <T>value !== undefined && <T>value !== null
|
return <T>value !== undefined && <T>value !== null
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isNotDefined = <T>(value: T | undefined | null): value is T => {
|
|
||||||
return <T>value === undefined || <T>value === null
|
|
||||||
}
|
|
||||||
|
|
||||||
export const preventUserFromRefreshing = (e: BeforeUnloadEvent) => {
|
export const preventUserFromRefreshing = (e: BeforeUnloadEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
e.returnValue = ''
|
e.returnValue = ''
|
||||||
@@ -91,3 +87,26 @@ export const omit: Omit = (obj, ...keys) => {
|
|||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const uploadFile = async (file: File, key: string) => {
|
||||||
|
const res = await fetch(
|
||||||
|
`/api/storage/upload-url?key=${encodeURIComponent(
|
||||||
|
key
|
||||||
|
)}&fileType=${encodeURIComponent(file.type)}`
|
||||||
|
)
|
||||||
|
const { url, fields } = await res.json()
|
||||||
|
const formData = new FormData()
|
||||||
|
|
||||||
|
Object.entries({ ...fields, file }).forEach(([key, value]) => {
|
||||||
|
formData.append(key, value as string | Blob)
|
||||||
|
})
|
||||||
|
|
||||||
|
const upload = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: upload.ok ? `${url}/${key}` : null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- A unique constraint covering the columns `[stripeId]` on the table `User` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterEnum
|
||||||
|
-- This migration adds more than one value to an enum.
|
||||||
|
-- With PostgreSQL versions 11 and earlier, this is not possible
|
||||||
|
-- in a single migration. This can be worked around by creating
|
||||||
|
-- multiple migrations, each migration adding only one value to
|
||||||
|
-- the enum.
|
||||||
|
|
||||||
|
|
||||||
|
ALTER TYPE "Plan" ADD VALUE 'LIFETIME';
|
||||||
|
ALTER TYPE "Plan" ADD VALUE 'OFFERED';
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" ADD COLUMN "stripeId" TEXT;
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_stripeId_key" ON "User"("stripeId");
|
||||||
@@ -48,11 +48,14 @@ model User {
|
|||||||
typebots Typebot[]
|
typebots Typebot[]
|
||||||
folders DashboardFolder[]
|
folders DashboardFolder[]
|
||||||
plan Plan @default(FREE)
|
plan Plan @default(FREE)
|
||||||
|
stripeId String? @unique
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Plan {
|
enum Plan {
|
||||||
FREE
|
FREE
|
||||||
PRO
|
PRO
|
||||||
|
LIFETIME
|
||||||
|
OFFERED
|
||||||
}
|
}
|
||||||
|
|
||||||
model VerificationToken {
|
model VerificationToken {
|
||||||
|
|||||||
155
yarn.lock
155
yarn.lock
@@ -1286,6 +1286,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz#7f698254aadf921e48dda8c0a6b304026b8a9323"
|
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz#7f698254aadf921e48dda8c0a6b304026b8a9323"
|
||||||
integrity sha512-JLo+Y592QzIE+q7Dl2pMUtt4q8SKYI5jDrZxrozEQxnGVOyYE+GWK9eLkwTaeN9DDctlaRAQ3TBmzZ1qdLE30A==
|
integrity sha512-JLo+Y592QzIE+q7Dl2pMUtt4q8SKYI5jDrZxrozEQxnGVOyYE+GWK9eLkwTaeN9DDctlaRAQ3TBmzZ1qdLE30A==
|
||||||
|
|
||||||
|
"@socket.io/component-emitter@~3.0.0":
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.0.0.tgz#8863915676f837d9dad7b76f50cb500c1e9422e9"
|
||||||
|
integrity sha512-2pTGuibAXJswAPJjaKisthqS/NOK5ypG4LYT6tEAV0S/mxW0zOIvYvGK0V8w8+SHxAm6vRMSjqSalFXeBAqs+Q==
|
||||||
|
|
||||||
"@testing-library/cypress@^8.0.2":
|
"@testing-library/cypress@^8.0.2":
|
||||||
version "8.0.2"
|
version "8.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@testing-library/cypress/-/cypress-8.0.2.tgz#b13f0ff2424dec4368b6670dfbfb7e43af8eefc9"
|
resolved "https://registry.yarnpkg.com/@testing-library/cypress/-/cypress-8.0.2.tgz#b13f0ff2424dec4368b6670dfbfb7e43af8eefc9"
|
||||||
@@ -1359,6 +1364,13 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc"
|
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc"
|
||||||
integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==
|
integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig==
|
||||||
|
|
||||||
|
"@types/engine.io@*":
|
||||||
|
version "3.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/engine.io/-/engine.io-3.1.7.tgz#86e541a5dc52fb7e97735383564a6ae4cfe2e8f5"
|
||||||
|
integrity sha512-qNjVXcrp+1sS8YpRUa714r0pgzOwESdW5UjHL7D/2ZFdBX0BXUXtg1LUrp+ylvqbvMcMWUy73YpRoxPN2VoKAQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/estree@*":
|
"@types/estree@*":
|
||||||
version "0.0.50"
|
version "0.0.50"
|
||||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83"
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83"
|
||||||
@@ -1420,6 +1432,21 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.178.tgz#341f6d2247db528d4a13ddbb374bcdc80406f4f8"
|
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.178.tgz#341f6d2247db528d4a13ddbb374bcdc80406f4f8"
|
||||||
integrity sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==
|
integrity sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw==
|
||||||
|
|
||||||
|
"@types/micro-cors@^0.1.2":
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/micro-cors/-/micro-cors-0.1.2.tgz#3004f5935d06b6f03016aa5975e8851e803d8374"
|
||||||
|
integrity sha512-dlNOHHaatKcuYXGMHkqICtFdthd+3tE8pFIrDTbDBYAtlTb7HzYh+NeEgfNWiARgOnsIOuoh2L2XR4UHow84nw==
|
||||||
|
dependencies:
|
||||||
|
"@types/micro" "*"
|
||||||
|
|
||||||
|
"@types/micro@*":
|
||||||
|
version "7.3.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/micro/-/micro-7.3.6.tgz#7d68eb5a780ac4761e3b80687b4ee7328ebc3f2e"
|
||||||
|
integrity sha512-rZHvZ3+Ev3cGJJSy/wtSiXZmafU8guI07PHXf4ku9sQLfDuFALHMCiV+LuH4VOaeMMMnRs8nqxU392gxfn661g==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" "*"
|
||||||
|
"@types/socket.io" "2.1.13"
|
||||||
|
|
||||||
"@types/mime-types@^2.1.0":
|
"@types/mime-types@^2.1.0":
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.1.tgz#d9ba43490fa3a3df958759adf69396c3532cf2c1"
|
resolved "https://registry.yarnpkg.com/@types/mime-types/-/mime-types-2.1.1.tgz#d9ba43490fa3a3df958759adf69396c3532cf2c1"
|
||||||
@@ -1430,6 +1457,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.0.tgz#62797cee3b8b497f6547503b2312254d4fe3c2bb"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.0.tgz#62797cee3b8b497f6547503b2312254d4fe3c2bb"
|
||||||
integrity sha512-eMhwJXc931Ihh4tkU+Y7GiLzT/y/DBNpNtr4yU9O2w3SYBsr9NaOPhQlLKRmoWtI54uNwuo0IOUFQjVOTZYRvw==
|
integrity sha512-eMhwJXc931Ihh4tkU+Y7GiLzT/y/DBNpNtr4yU9O2w3SYBsr9NaOPhQlLKRmoWtI54uNwuo0IOUFQjVOTZYRvw==
|
||||||
|
|
||||||
|
"@types/node@>=8.1.0":
|
||||||
|
version "17.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.5.tgz#57ca67ec4e57ad9e4ef5a6bab48a15387a1c83e0"
|
||||||
|
integrity sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw==
|
||||||
|
|
||||||
"@types/node@^14.14.31":
|
"@types/node@^14.14.31":
|
||||||
version "14.18.0"
|
version "14.18.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.0.tgz#98df2397f6936bfbff4f089e40e06fa5dd88d32a"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.0.tgz#98df2397f6936bfbff4f089e40e06fa5dd88d32a"
|
||||||
@@ -1521,6 +1553,22 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef"
|
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.3.tgz#ff5e2f1902969d305225a047c8a0fd5c915cebef"
|
||||||
integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==
|
integrity sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==
|
||||||
|
|
||||||
|
"@types/socket.io-parser@*":
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/socket.io-parser/-/socket.io-parser-3.0.0.tgz#9726d3ab9235757a0a30dd5ccf8975dce54e5e2c"
|
||||||
|
integrity sha512-Ry/rbTE6HQNL9eu3LpL1Ocup5VexXu1bSSGlSho/IR5LuRc8YvxwSNJ3JxqTltVJEATLbZkMQETSbxfKNgp4Ew==
|
||||||
|
dependencies:
|
||||||
|
socket.io-parser "*"
|
||||||
|
|
||||||
|
"@types/socket.io@2.1.13":
|
||||||
|
version "2.1.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/socket.io/-/socket.io-2.1.13.tgz#b6d694234e99956c96ff99e197eda824b6f9dc48"
|
||||||
|
integrity sha512-JRgH3nCgsWel4OPANkhH8TelpXvacAJ9VeryjuqCDiaVDMpLysd6sbt0dr6Z15pqH3p2YpOT3T1C5vQ+O/7uyg==
|
||||||
|
dependencies:
|
||||||
|
"@types/engine.io" "*"
|
||||||
|
"@types/node" "*"
|
||||||
|
"@types/socket.io-parser" "*"
|
||||||
|
|
||||||
"@types/testing-library__cypress@^5.0.9":
|
"@types/testing-library__cypress@^5.0.9":
|
||||||
version "5.0.9"
|
version "5.0.9"
|
||||||
resolved "https://registry.yarnpkg.com/@types/testing-library__cypress/-/testing-library__cypress-5.0.9.tgz#c65f2be0cbb7f11556c1a35fd767d8dd6d1dff23"
|
resolved "https://registry.yarnpkg.com/@types/testing-library__cypress/-/testing-library__cypress-5.0.9.tgz#c65f2be0cbb7f11556c1a35fd767d8dd6d1dff23"
|
||||||
@@ -2024,6 +2072,21 @@ available-typed-arrays@^1.0.5:
|
|||||||
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
|
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
|
||||||
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
|
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
|
||||||
|
|
||||||
|
aws-sdk@^2.1048.0:
|
||||||
|
version "2.1048.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1048.0.tgz#02f2f35e0f51dd4510462e05c7a48fd4649d33f8"
|
||||||
|
integrity sha512-mVwWo+Udiuc/yEZ/DgJQGqOEtfiQjgUdtshx/t6ISe3+jW3TF9hUACwADwx2Sr/fuJyyJ3QD5JYLt5Cw35wQpA==
|
||||||
|
dependencies:
|
||||||
|
buffer "4.9.2"
|
||||||
|
events "1.1.1"
|
||||||
|
ieee754 "1.1.13"
|
||||||
|
jmespath "0.15.0"
|
||||||
|
querystring "0.2.0"
|
||||||
|
sax "1.2.1"
|
||||||
|
url "0.10.3"
|
||||||
|
uuid "3.3.2"
|
||||||
|
xml2js "0.4.19"
|
||||||
|
|
||||||
aws-sign2@~0.7.0:
|
aws-sign2@~0.7.0:
|
||||||
version "0.7.0"
|
version "0.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
|
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
|
||||||
@@ -2238,6 +2301,15 @@ buffer-xor@^1.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
|
resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
|
||||||
integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=
|
integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=
|
||||||
|
|
||||||
|
buffer@4.9.2:
|
||||||
|
version "4.9.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8"
|
||||||
|
integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==
|
||||||
|
dependencies:
|
||||||
|
base64-js "^1.0.2"
|
||||||
|
ieee754 "^1.1.4"
|
||||||
|
isarray "^1.0.0"
|
||||||
|
|
||||||
buffer@5.6.0:
|
buffer@5.6.0:
|
||||||
version "5.6.0"
|
version "5.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786"
|
resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786"
|
||||||
@@ -2952,7 +3024,7 @@ debug@2, debug@^2.6.9:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "2.0.0"
|
ms "2.0.0"
|
||||||
|
|
||||||
debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2:
|
debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@~4.3.1:
|
||||||
version "4.3.3"
|
version "4.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
||||||
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
|
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
|
||||||
@@ -3574,6 +3646,11 @@ eventemitter3@^4.0.4:
|
|||||||
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
|
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
|
||||||
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
|
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
|
||||||
|
|
||||||
|
events@1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
|
||||||
|
integrity sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=
|
||||||
|
|
||||||
events@3.3.0:
|
events@3.3.0:
|
||||||
version "3.3.0"
|
version "3.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
|
resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400"
|
||||||
@@ -4149,6 +4226,11 @@ icss-utils@^5.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
|
resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae"
|
||||||
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
|
integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==
|
||||||
|
|
||||||
|
ieee754@1.1.13:
|
||||||
|
version "1.1.13"
|
||||||
|
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84"
|
||||||
|
integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
|
||||||
|
|
||||||
ieee754@^1.1.4:
|
ieee754@^1.1.4:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||||
@@ -4459,7 +4541,7 @@ is-weakref@^1.0.1:
|
|||||||
dependencies:
|
dependencies:
|
||||||
call-bind "^1.0.2"
|
call-bind "^1.0.2"
|
||||||
|
|
||||||
isarray@~1.0.0:
|
isarray@^1.0.0, isarray@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||||
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
||||||
@@ -4497,6 +4579,11 @@ jest-worker@^26.2.1:
|
|||||||
merge-stream "^2.0.0"
|
merge-stream "^2.0.0"
|
||||||
supports-color "^7.0.0"
|
supports-color "^7.0.0"
|
||||||
|
|
||||||
|
jmespath@0.15.0:
|
||||||
|
version "0.15.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/jmespath/-/jmespath-0.15.0.tgz#a3f222a9aae9f966f5d27c796510e28091764217"
|
||||||
|
integrity sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=
|
||||||
|
|
||||||
jose@^4.1.2, jose@^4.1.4:
|
jose@^4.1.2, jose@^4.1.4:
|
||||||
version "4.3.7"
|
version "4.3.7"
|
||||||
resolved "https://registry.yarnpkg.com/jose/-/jose-4.3.7.tgz#5000e4a2d41ae411a5abdd11e6baf63fc2973a69"
|
resolved "https://registry.yarnpkg.com/jose/-/jose-4.3.7.tgz#5000e4a2d41ae411a5abdd11e6baf63fc2973a69"
|
||||||
@@ -4850,6 +4937,11 @@ merge2@^1.3.0:
|
|||||||
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
|
||||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||||
|
|
||||||
|
micro-cors@^0.1.1:
|
||||||
|
version "0.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/micro-cors/-/micro-cors-0.1.1.tgz#af7a480182c114ffd1ada84ad9dffc52bb4f4054"
|
||||||
|
integrity sha512-6WqIahA5sbQR1Gjexp1VuWGFDKbZZleJb/gy1khNGk18a6iN1FdTcr3Q8twaxkV5H94RjxIBjirYbWCehpMBFw==
|
||||||
|
|
||||||
micromatch@^4.0.4:
|
micromatch@^4.0.4:
|
||||||
version "4.0.4"
|
version "4.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9"
|
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9"
|
||||||
@@ -5963,6 +6055,13 @@ puppeteer@^2.1.1:
|
|||||||
rimraf "^2.6.1"
|
rimraf "^2.6.1"
|
||||||
ws "^6.1.0"
|
ws "^6.1.0"
|
||||||
|
|
||||||
|
qs@^6.6.0:
|
||||||
|
version "6.10.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.2.tgz#c1431bea37fc5b24c5bdbafa20f16bdf2a4b9ffe"
|
||||||
|
integrity sha512-mSIdjzqznWgfd4pMii7sHtaYF8rx8861hBO80SraY5GT0XQibWZWJSid0avzHGkDIZLImux2S5mXO0Hfct2QCw==
|
||||||
|
dependencies:
|
||||||
|
side-channel "^1.0.4"
|
||||||
|
|
||||||
qs@~6.5.2:
|
qs@~6.5.2:
|
||||||
version "6.5.2"
|
version "6.5.2"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||||
@@ -6426,6 +6525,16 @@ safe-identifier@^0.4.2:
|
|||||||
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
|
||||||
|
|
||||||
|
sax@1.2.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.1.tgz#7b8e656190b228e81a66aea748480d828cd2d37a"
|
||||||
|
integrity sha1-e45lYZCyKOgaZq6nSEgNgozS03o=
|
||||||
|
|
||||||
|
sax@>=0.6.0:
|
||||||
|
version "1.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||||
|
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
||||||
|
|
||||||
scheduler@^0.20.2:
|
scheduler@^0.20.2:
|
||||||
version "0.20.2"
|
version "0.20.2"
|
||||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
|
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
|
||||||
@@ -6609,6 +6718,14 @@ slice-ansi@^4.0.0:
|
|||||||
astral-regex "^2.0.0"
|
astral-regex "^2.0.0"
|
||||||
is-fullwidth-code-point "^3.0.0"
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
|
||||||
|
socket.io-parser@*:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.1.1.tgz#0ad53d980781cab1eabe320417d8480c0133e62d"
|
||||||
|
integrity sha512-USQVLSkDWE5nbcY760ExdKaJxCE65kcsG/8k5FDGZVVxpD1pA7hABYXYkCUvxUuYYh/+uQw0N/fvBzfT8o07KA==
|
||||||
|
dependencies:
|
||||||
|
"@socket.io/component-emitter" "~3.0.0"
|
||||||
|
debug "~4.3.1"
|
||||||
|
|
||||||
source-map-js@^1.0.1:
|
source-map-js@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf"
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf"
|
||||||
@@ -6860,6 +6977,14 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
|
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
|
||||||
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
|
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
|
||||||
|
|
||||||
|
stripe@^8.195.0:
|
||||||
|
version "8.195.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/stripe/-/stripe-8.195.0.tgz#4d253e247aadb64d972488da9481fff743b58a11"
|
||||||
|
integrity sha512-pXEZFNJb4p9uZ69+B4A+zJEmBiFw3BzNG51ctPxUZij7ghFTnk2/RuUHmSGto2XVCcC46uG75czXVAvCUkOGtQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/node" ">=8.1.0"
|
||||||
|
qs "^6.6.0"
|
||||||
|
|
||||||
style-inject@^0.3.0:
|
style-inject@^0.3.0:
|
||||||
version "0.3.0"
|
version "0.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/style-inject/-/style-inject-0.3.0.tgz#d21c477affec91811cc82355832a700d22bf8dd3"
|
resolved "https://registry.yarnpkg.com/style-inject/-/style-inject-0.3.0.tgz#d21c477affec91811cc82355832a700d22bf8dd3"
|
||||||
@@ -7336,6 +7461,14 @@ uri-js@^4.2.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
punycode "^2.1.0"
|
punycode "^2.1.0"
|
||||||
|
|
||||||
|
url@0.10.3:
|
||||||
|
version "0.10.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64"
|
||||||
|
integrity sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=
|
||||||
|
dependencies:
|
||||||
|
punycode "1.3.2"
|
||||||
|
querystring "0.2.0"
|
||||||
|
|
||||||
url@^0.11.0:
|
url@^0.11.0:
|
||||||
version "0.11.0"
|
version "0.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
|
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
|
||||||
@@ -7393,6 +7526,11 @@ util@0.12.4, util@^0.12.0:
|
|||||||
safe-buffer "^5.1.2"
|
safe-buffer "^5.1.2"
|
||||||
which-typed-array "^1.1.2"
|
which-typed-array "^1.1.2"
|
||||||
|
|
||||||
|
uuid@3.3.2:
|
||||||
|
version "3.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
||||||
|
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
|
||||||
|
|
||||||
uuid@^8.3.2:
|
uuid@^8.3.2:
|
||||||
version "8.3.2"
|
version "8.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||||
@@ -7526,6 +7664,19 @@ ws@^6.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
async-limiter "~1.0.0"
|
async-limiter "~1.0.0"
|
||||||
|
|
||||||
|
xml2js@0.4.19:
|
||||||
|
version "0.4.19"
|
||||||
|
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
|
||||||
|
integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==
|
||||||
|
dependencies:
|
||||||
|
sax ">=0.6.0"
|
||||||
|
xmlbuilder "~9.0.1"
|
||||||
|
|
||||||
|
xmlbuilder@~9.0.1:
|
||||||
|
version "9.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
|
||||||
|
integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
|
||||||
|
|
||||||
xtend@^4.0.2:
|
xtend@^4.0.2:
|
||||||
version "4.0.2"
|
version "4.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||||
|
|||||||
Reference in New Issue
Block a user