feat(dashboard): 🛂 Limit create folder to Pro user
This commit is contained in:
@ -1,6 +1,5 @@
|
||||
import { DashboardFolder } from '.prisma/client'
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
Heading,
|
||||
HStack,
|
||||
@ -11,7 +10,6 @@ import {
|
||||
useToast,
|
||||
Wrap,
|
||||
} from '@chakra-ui/react'
|
||||
import { FolderPlusIcon } from 'assets/icons'
|
||||
import { useTypebotDnd } from 'contexts/TypebotDndContext'
|
||||
import { Typebot } from 'models'
|
||||
import React, { useState } from 'react'
|
||||
@ -19,6 +17,7 @@ import { createFolder, useFolders } from 'services/folders'
|
||||
import { patchTypebot, useTypebots } from 'services/typebots'
|
||||
import { BackButton } from './FolderContent/BackButton'
|
||||
import { CreateBotButton } from './FolderContent/CreateBotButton'
|
||||
import { CreateFolderButton } from './FolderContent/CreateFolderButton'
|
||||
import { ButtonSkeleton, FolderButton } from './FolderContent/FolderButton'
|
||||
import { TypebotButton } from './FolderContent/TypebotButton'
|
||||
import { TypebotCardOverlay } from './FolderContent/TypebotButtonOverlay'
|
||||
@ -152,13 +151,10 @@ export const FolderContent = ({ folder }: Props) => {
|
||||
<Stack>
|
||||
<HStack>
|
||||
{folder && <BackButton id={folder.parentFolderId} />}
|
||||
<Button
|
||||
leftIcon={<FolderPlusIcon />}
|
||||
<CreateFolderButton
|
||||
onClick={handleCreateFolder}
|
||||
isLoading={isCreatingFolder || isFolderLoading}
|
||||
>
|
||||
Create a folder
|
||||
</Button>
|
||||
/>
|
||||
</HStack>
|
||||
<Wrap spacing={4}>
|
||||
<CreateBotButton
|
||||
|
@ -0,0 +1,39 @@
|
||||
import { Button, HStack, Tag, useDisclosure, Text } from '@chakra-ui/react'
|
||||
import { FolderPlusIcon } from 'assets/icons'
|
||||
import { UpgradeModal } from 'components/shared/modals/UpgradeModal.'
|
||||
import { LimitReached } from 'components/shared/modals/UpgradeModal./UpgradeModal'
|
||||
import { useUser } from 'contexts/UserContext'
|
||||
import React from 'react'
|
||||
import { isFreePlan } from 'services/user'
|
||||
|
||||
type Props = { isLoading: boolean; onClick: () => void }
|
||||
|
||||
export const CreateFolderButton = ({ isLoading, onClick }: Props) => {
|
||||
const { user } = useUser()
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
|
||||
const handleClick = () => {
|
||||
if (isFreePlan(user)) return onOpen()
|
||||
onClick()
|
||||
}
|
||||
return (
|
||||
<Button
|
||||
leftIcon={<FolderPlusIcon />}
|
||||
onClick={handleClick}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
<HStack>
|
||||
<Text>Create a folder</Text>
|
||||
{isFreePlan(user) && <Tag colorScheme="orange">Pro</Tag>}
|
||||
</HStack>
|
||||
{user && (
|
||||
<UpgradeModal
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
user={user}
|
||||
type={LimitReached.FOLDER}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
)
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import { Button, ButtonProps } from '@chakra-ui/react'
|
||||
import * as React from 'react'
|
||||
|
||||
export const ActionButton = (props: ButtonProps) => (
|
||||
<Button
|
||||
colorScheme="blue"
|
||||
size="lg"
|
||||
w="full"
|
||||
fontWeight="extrabold"
|
||||
py={{ md: '8' }}
|
||||
{...props}
|
||||
/>
|
||||
)
|
28
apps/builder/components/shared/modals/UpgradeModal./Card.tsx
Normal file
28
apps/builder/components/shared/modals/UpgradeModal./Card.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import { Box, BoxProps, useColorModeValue } from '@chakra-ui/react'
|
||||
import * as React from 'react'
|
||||
import { CardBadge } from './CardBadge'
|
||||
|
||||
export interface CardProps extends BoxProps {
|
||||
isPopular?: boolean
|
||||
}
|
||||
|
||||
export const Card = (props: CardProps) => {
|
||||
const { children, isPopular, ...rest } = props
|
||||
return (
|
||||
<Box
|
||||
bg={useColorModeValue('white', 'gray.700')}
|
||||
position="relative"
|
||||
px="6"
|
||||
pb="6"
|
||||
pt="16"
|
||||
overflow="hidden"
|
||||
shadow="lg"
|
||||
maxW="md"
|
||||
width="100%"
|
||||
{...rest}
|
||||
>
|
||||
{isPopular && <CardBadge>Popular</CardBadge>}
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
import { Flex, FlexProps, Text, useColorModeValue } from '@chakra-ui/react'
|
||||
import * as React from 'react'
|
||||
|
||||
export const CardBadge = (props: FlexProps) => {
|
||||
const { children, ...flexProps } = props
|
||||
return (
|
||||
<Flex
|
||||
bg={useColorModeValue('green.500', 'green.200')}
|
||||
position="absolute"
|
||||
right={-20}
|
||||
top={6}
|
||||
width="240px"
|
||||
transform="rotate(45deg)"
|
||||
py={2}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
{...flexProps}
|
||||
>
|
||||
<Text
|
||||
fontSize="xs"
|
||||
textTransform="uppercase"
|
||||
fontWeight="bold"
|
||||
letterSpacing="wider"
|
||||
color={useColorModeValue('white', 'gray.800')}
|
||||
>
|
||||
{children}
|
||||
</Text>
|
||||
</Flex>
|
||||
)
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
import {
|
||||
Flex,
|
||||
Heading,
|
||||
List,
|
||||
ListIcon,
|
||||
ListItem,
|
||||
Text,
|
||||
useColorModeValue,
|
||||
VStack,
|
||||
} from '@chakra-ui/react'
|
||||
import { CheckIcon } from 'assets/icons'
|
||||
import * as React from 'react'
|
||||
import { Card, CardProps } from './Card'
|
||||
|
||||
export interface PricingCardData {
|
||||
features: string[]
|
||||
name: string
|
||||
price: string
|
||||
}
|
||||
|
||||
interface PricingCardProps extends CardProps {
|
||||
data: PricingCardData
|
||||
button: React.ReactElement
|
||||
}
|
||||
|
||||
export const PricingCard = (props: PricingCardProps) => {
|
||||
const { data, button, ...rest } = props
|
||||
const { features, price, name } = data
|
||||
const accentColor = useColorModeValue('blue.500', 'blue.200')
|
||||
|
||||
return (
|
||||
<Card rounded={{ sm: 'xl' }} {...rest}>
|
||||
<VStack spacing={6}>
|
||||
<Heading size="md" fontWeight="extrabold">
|
||||
{name}
|
||||
</Heading>
|
||||
</VStack>
|
||||
<Flex
|
||||
align="flex-end"
|
||||
justify="center"
|
||||
fontWeight="extrabold"
|
||||
color={accentColor}
|
||||
my="8"
|
||||
>
|
||||
<Heading size="3xl" fontWeight="inherit" lineHeight="0.9em">
|
||||
{price}
|
||||
</Heading>
|
||||
<Text fontWeight="inherit" fontSize="2xl">
|
||||
/ mo
|
||||
</Text>
|
||||
</Flex>
|
||||
<List spacing="4" mb="8" maxW="30ch" mx="auto">
|
||||
{features.map((feature, index) => (
|
||||
<ListItem fontWeight="medium" key={index}>
|
||||
<ListIcon
|
||||
fontSize="xl"
|
||||
as={CheckIcon}
|
||||
marginEnd={2}
|
||||
color={accentColor}
|
||||
/>
|
||||
{feature}
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
{button}
|
||||
</Card>
|
||||
)
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import {
|
||||
Alert,
|
||||
AlertIcon,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
Stack,
|
||||
} from '@chakra-ui/react'
|
||||
import { PricingCard } from './PricingCard'
|
||||
import { ActionButton } from './ActionButton'
|
||||
import { User } from 'db'
|
||||
import { pay } from 'services/stripe'
|
||||
|
||||
export enum LimitReached {
|
||||
BRAND = 'Remove branding',
|
||||
CUSTOM_DOMAIN = 'Custom domain',
|
||||
FOLDER = 'Create folders',
|
||||
ANALYTICS = 'Unlock analytics',
|
||||
}
|
||||
|
||||
type UpgradeModalProps = {
|
||||
user: User
|
||||
type: LimitReached
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export const UpgradeModal = ({
|
||||
type,
|
||||
user,
|
||||
onClose,
|
||||
isOpen,
|
||||
}: UpgradeModalProps) => {
|
||||
const [payLoading, setPayLoading] = useState(false)
|
||||
const [userLanguage, setUserLanguage] = useState<string>('en')
|
||||
|
||||
useEffect(() => {
|
||||
setUserLanguage(navigator.language.toLowerCase())
|
||||
}, [])
|
||||
|
||||
let limitLabel
|
||||
switch (type) {
|
||||
case LimitReached.BRAND: {
|
||||
limitLabel = "You can't hide Typebot brand on the Basic plan"
|
||||
break
|
||||
}
|
||||
case LimitReached.CUSTOM_DOMAIN: {
|
||||
limitLabel = "You can't add your domain with the Basic plan"
|
||||
break
|
||||
}
|
||||
case LimitReached.FOLDER: {
|
||||
limitLabel = "You can't create folders with the basic plan"
|
||||
}
|
||||
}
|
||||
|
||||
const handlePayClick = async () => {
|
||||
if (!user) return
|
||||
setPayLoading(true)
|
||||
await pay(user)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="xl">
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>Upgrade to Pro plan</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody as={Stack} spacing={6} alignItems="center">
|
||||
{limitLabel && (
|
||||
<Alert status="warning" rounded="md">
|
||||
<AlertIcon />
|
||||
{limitLabel}
|
||||
</Alert>
|
||||
)}
|
||||
<PricingCard
|
||||
data={{
|
||||
price: userLanguage.includes('fr') ? '25€' : '$30',
|
||||
name: 'Pro plan',
|
||||
features: [
|
||||
'Branding removed',
|
||||
'View incomplete submissions',
|
||||
'In-depth drop off analytics',
|
||||
'Custom domains',
|
||||
'Organize typebots in folders',
|
||||
'Unlimited uploads',
|
||||
'Custom Google Analytics events',
|
||||
],
|
||||
}}
|
||||
button={
|
||||
<ActionButton onClick={handlePayClick} isLoading={payLoading}>
|
||||
Upgrade now
|
||||
</ActionButton>
|
||||
}
|
||||
/>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { UpgradeModal } from './UpgradeModal'
|
Reference in New Issue
Block a user