update pay function

This commit is contained in:
2025-11-22 11:41:56 +08:00
parent a4b634abff
commit d8e4c737c5
397 changed files with 19572 additions and 9326 deletions

View File

@@ -0,0 +1,144 @@
'use client';
import {
renderThumb,
renderTrack,
renderView
} from '@/components/scrollbar/Scrollbar';
import Content from '@/components/sidebar/components/Content';
import { ApiKeyContext } from '@/contexts/layout';
import { IRoute } from '@/types/types';
import { isWindowAvailable } from '@/utils/navigation';
import {
Box,
Flex,
Drawer,
DrawerBody,
Icon,
useColorModeValue,
DrawerOverlay,
useDisclosure,
DrawerContent,
DrawerCloseButton
} from '@chakra-ui/react';
import React, { PropsWithChildren, useContext } from 'react';
import { Scrollbars } from 'react-custom-scrollbars-2';
import { IoMenuOutline } from 'react-icons/io5';
export interface SidebarProps extends PropsWithChildren {
routes: IRoute[];
[x: string]: any;
}
function Sidebar(props: SidebarProps) {
const { routes } = props;
const { apiKey, setApiKey } = useContext(ApiKeyContext);
let variantChange = '0.2s linear';
let shadow = useColorModeValue(
'14px 17px 40px 4px rgba(112, 144, 176, 0.08)',
'unset'
);
let sidebarBg = useColorModeValue('white', 'navy.800');
let sidebarRadius = '14px';
let sidebarMargins = '0px';
return (
<Box display={{ base: 'none', xl: 'block' }} position="fixed" minH="100%">
<Box
bg={sidebarBg}
transition={variantChange}
w="285px"
ms={{
sm: '16px'
}}
my={{
sm: '16px'
}}
h="calc(100vh - 32px)"
m={sidebarMargins}
borderRadius={sidebarRadius}
minH="100%"
overflowX="hidden"
boxShadow={shadow}
>
<Scrollbars
autoHide
renderTrackVertical={renderTrack}
renderThumbVertical={renderThumb}
renderView={renderView}
universal={true}
>
<Content setApiKey={setApiKey} routes={routes} />
</Scrollbars>
</Box>
</Box>
);
}
// -------------- Sidebar Function for Navbar burger --------------
export function SidebarResponsive(props: SidebarProps) {
let sidebarBackgroundColor = useColorModeValue('white', 'navy.800');
let menuColor = useColorModeValue('gray.400', 'white');
const { apiKey, setApiKey } = useContext(ApiKeyContext);
// SIDEBAR
const { isOpen, onOpen, onClose } = useDisclosure();
const { routes } = props;
return (
<Flex display={{ sm: 'flex', xl: 'none' }} alignItems="center">
<Flex w="max-content" h="max-content" onClick={onOpen}>
<Icon
as={IoMenuOutline}
color={menuColor}
my="auto"
w="20px"
h="20px"
me="10px"
_hover={{ cursor: 'pointer' }}
/>
</Flex>
<Drawer
isOpen={isOpen}
onClose={onClose}
placement={
isWindowAvailable() && document.documentElement.dir === 'rtl'
? 'right'
: 'left'
}
>
<DrawerOverlay />
<DrawerContent
w="285px"
maxW="285px"
ms={{
sm: '16px'
}}
my={{
sm: '16px'
}}
borderRadius="16px"
bg={sidebarBackgroundColor}
>
<DrawerCloseButton
zIndex="3"
onClick={onClose}
_focus={{ boxShadow: 'none' }}
_hover={{ boxShadow: 'none' }}
/>
<DrawerBody maxW="285px" px="0rem" pb="0">
<Scrollbars
autoHide
renderTrackVertical={renderTrack}
renderThumbVertical={renderThumb}
renderView={renderView}
universal={true}
>
<Content setApiKey={setApiKey} routes={routes} />
</Scrollbars>
</DrawerBody>
</DrawerContent>
</Drawer>
</Flex>
);
}
export default Sidebar;

View File

@@ -0,0 +1,38 @@
'use client';
import logo from '/public/logo-horizon-boilerplate.png';
import { HSeparator } from '@/components/separator/Separator';
import { Flex, Image, Link, Text } from '@chakra-ui/react';
export function SidebarBrand() {
return (
<Flex alignItems="center" flexDirection="column" ps="24px">
<Link
display={'flex'}
alignItems="center"
justifyContent={'center'}
href="/"
mb="34px"
>
<Image
alt=" "
w="36px"
src={logo.src}
/>
<Text
ms="8px"
me="10px"
fontWeight="800"
fontSize={'17px'}
letterSpacing="-0.2px"
color="#120F43"
>
Horizon UI Boilerplate
</Text>
</Link>
<HSeparator mb="20px" w="310px" />
</Flex>
);
}
export default SidebarBrand;

View File

@@ -0,0 +1,111 @@
'use client';
// Custom components
import { useSupabase } from '@/app/supabase-provider';
import Brand from '@/components/sidebar/components/Brand';
import Links from '@/components/sidebar/components/Links';
import SidebarCard from '@/components/sidebar/components/SidebarCard';
import { UserContext } from '@/contexts/layout';
import { IRoute } from '@/types/types';
import {
Avatar,
Box,
Button,
Flex,
Icon,
Stack,
Text,
useColorModeValue
} from '@chakra-ui/react';
import { useRouter } from 'next/navigation';
import { PropsWithChildren, useContext } from 'react';
import { FiLogOut } from 'react-icons/fi';
// FUNCTIONS
interface SidebarContent extends PropsWithChildren {
routes: IRoute[];
[x: string]: any;
}
function SidebarContent(props: SidebarContent) {
const router = useRouter();
const { supabase } = useSupabase();
const { routes, setApiKey } = props;
const user = useContext(UserContext);
console.log(user.user_metadata.avatar_url);
const textColor = useColorModeValue('#120F43', 'white');
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.300');
const shadowPillBar = useColorModeValue(
'4px 17px 40px 4px rgba(112, 144, 176, 0.08)',
'none'
);
return (
<Flex
direction="column"
height="100%"
pt="36px"
pb="26px"
borderRadius="30px"
maxW="285px"
w="100%"
>
<Brand />
<Stack direction="column" mb="auto" mt="8px" ps="20px" pe="16px">
<Box ps="0px" pe={{ md: '0px', '2xl': '0px' }}>
<Links routes={routes} />
</Box>
</Stack>
<Box
mt="60px"
width={'100%'}
display={'flex'}
justifyContent={'center'}
ps="20px"
pe="20px"
>
<SidebarCard />
</Box>
<Flex
mt="20px"
justifyContent="center"
alignItems="center"
boxShadow={shadowPillBar}
borderRadius="30px"
p="14px"
px="34px"
>
<Avatar
h="34px"
w="34px"
me="10px"
src={user.user_metadata.avatar_url}
/>
<Text color={textColor} fontSize="sm" fontWeight="700" me="10px">
{user.user_metadata.full_name ? user.user_metadata.full_name : 'User'}
</Text>
<Button
ms="auto"
variant="transparent"
border="1px solid"
borderColor={borderColor}
borderRadius="full"
w="34px"
h="34px"
px="0px"
minW="34px"
justifyContent={'center'}
alignItems="center"
onClick={(e) => {
supabase.auth.signOut();
router.push('/');
}}
>
<Icon as={FiLogOut} width="16px" height="16px" color="inherit" />
</Button>
</Flex>
</Flex>
);
}
export default SidebarContent;

View File

@@ -0,0 +1,823 @@
'use client';
/* eslint-disable */
import NavLink from '@/components/link/NavLink';
import {
PlanContext,
ProductsContext,
UserContext,
UserDetailsContext
} from '@/contexts/layout';
import modalImage from '@/public/Modal.png';
import { IRoute } from '@/types/types';
import { Database } from '@/types_db';
import { getRedirectMethod } from '@/utils/auth-helpers/settings';
import { getErrorRedirect } from '@/utils/helpers';
import { getStripe } from '@/utils/stripe/client';
import { checkoutWithStripe } from '@/utils/stripe/server';
import {
Accordion,
AccordionButton,
AccordionIcon,
AccordionItem,
AccordionPanel,
Badge,
Box,
Button,
Flex,
HStack,
Icon,
Image,
List,
ListItem,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalOverlay,
Text,
useColorModeValue,
useDisclosure
} from '@chakra-ui/react';
import { usePathname, useRouter } from 'next/navigation';
import { PropsWithChildren, useCallback, useContext, useState } from 'react';
import { FaCircle } from 'react-icons/fa';
import { IoIosStar, IoMdAdd } from 'react-icons/io';
import { MdCheckCircle, MdChevronRight } from 'react-icons/md';
interface SidebarLinksProps extends PropsWithChildren {
routes: IRoute[];
[x: string]: any;
}
type Price = Database['public']['Tables']['prices']['Row'];
export function SidebarLinks(props: SidebarLinksProps) {
const pathname = usePathname();
const textColor = useColorModeValue('#120F43', 'white');
let activeColor = useColorModeValue('#120F43', 'white');
let inactiveColor = useColorModeValue('gray.500', 'gray.500');
let borderColor = useColorModeValue('gray.200', 'whiteAlpha.300');
let activeIcon = useColorModeValue('brand.500', 'white');
let iconColor = useColorModeValue('#120F43', 'white');
const { isOpen, onOpen, onClose } = useDisclosure();
// verifies if routeName is the one active (in browser input)
const activeRoute = useCallback(
(route: string | { [x: string]: any }) => {
let foundActive = 0;
if (typeof route === 'string') {
return pathname?.includes(route);
} else if (route?.items) {
route.items.map((item: { [x: string]: any }) => {
if (pathname?.includes(item.path)) foundActive = foundActive + 1;
});
}
if (foundActive > 0) return true;
else return false;
},
[pathname]
);
const router = getRedirectMethod() === 'client' ? useRouter() : null;
const { routes } = props;
const user = useContext(UserContext);
const [priceIdLoading, setPriceIdLoading] = useState<string>();
const { plan, setPlan } = useContext(PlanContext);
const products = useContext(ProductsContext);
const currentPath = usePathname();
// this function creates the links and collapses that appear in the sidebar (left menu)
const createLinks = (routes: IRoute[]) => {
const handleCheckout = async (price: Price) => {
setPriceIdLoading(price.id);
if (!user) {
setPriceIdLoading(undefined);
return router.push('/dashboard/signin/signup');
}
const { errorRedirect, sessionId } = await checkoutWithStripe(
price,
currentPath
);
if (errorRedirect) {
setPriceIdLoading(undefined);
return router.push(errorRedirect);
}
if (!sessionId) {
setPriceIdLoading(undefined);
return router.push(
getErrorRedirect(
currentPath,
'An unknown error occurred.',
'Please try again later or contact a system administrator.'
)
);
}
const stripe = await getStripe();
stripe?.redirectToCheckout({ sessionId });
setPriceIdLoading(undefined);
};
return routes.map((route, key) => {
if (route.collapse && !route.invisible) {
return (
<Accordion
allowToggle
defaultIndex={activeRoute(route) ? 0 : 1}
key={key}
>
<AccordionItem border="none" mb="14px" key={key}>
<AccordionButton
display="flex"
alignItems="center"
mb="4px"
justifyContent="center"
_hover={{
bg: 'unset'
}}
_focus={{
boxShadow: 'none'
}}
borderRadius="8px"
w="100%"
py="0px"
ms={0}
>
{route.icon ? (
<Flex align="center" justifyContent="space-between" w="100%">
<HStack spacing={activeRoute(route) ? '22px' : '26px'}>
<Flex
w="100%"
alignItems="center"
justifyContent="center"
>
<Box
color={
activeRoute(route) ? activeIcon : inactiveColor
}
me="12px"
mt="6px"
>
{route.icon}
</Box>
<Text
me="auto"
color={activeRoute(route) ? activeColor : 'gray.500'}
fontWeight="500"
letterSpacing="0px"
fontSize="sm"
>
{route.name}
</Text>
</Flex>
</HStack>
<AccordionIcon ms="auto" color={'gray.500'} />
</Flex>
) : (
<Flex pt="0px" pb="10px" alignItems="center" w="100%">
<HStack
spacing={
activeRoute(route.path.toLowerCase()) ? '22px' : '26px'
}
ps="32px"
>
<Text
me="auto"
color={
activeRoute(route.path.toLowerCase())
? activeColor
: inactiveColor
}
fontWeight="500"
letterSpacing="0px"
fontSize="sm"
>
{route.name}
</Text>
</HStack>
<AccordionIcon ms="auto" color={'gray.500'} />
</Flex>
)}
</AccordionButton>
<AccordionPanel py="0px" ps={'8px'}>
<List>
{
route.icon && route.items
? createLinks(route.items) // for bullet accordion links
: route.items
? createAccordionLinks(route.items)
: '' // for non-bullet accordion links
}
</List>
</AccordionPanel>
</AccordionItem>
</Accordion>
);
} else if (!route.invisible) {
return (
<Box key={key}>
{route.icon ? (
<Flex
align="center"
justifyContent="space-between"
w="100%"
maxW="100%"
ps="17px"
mb="0px"
>
<HStack
w="100%"
mb="14px"
spacing={
activeRoute(route.path.toLowerCase()) ? '22px' : '26px'
}
>
{route.path.includes('premium') && !props.subscription ? (
<Flex w="100%">
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay bg="rgba(0, 0, 0, 0.85)" />
<ModalContent
mx="8px"
bg="transparent"
boxShadow="unset"
maxW="unset"
w="unset"
>
<ModalBody p="0px" position={'relative'}>
<Flex>
<Image
display={{ base: 'none', md: 'block' }}
zIndex="98"
borderLeftRadius="16px"
src={modalImage.src}
w="340px"
/>
<Flex
bg="white"
borderLeftRadius={{ base: '16px', md: '0px' }}
borderRightRadius="16px"
direction={'column'}
px={{ base: '30px', md: '42px' }}
py="34px"
w={{ md: '412px', lg: '456px' }}
minW={{ md: '412px', lg: '456px' }}
>
<Text
fontSize="26px"
fontWeight={'800'}
color={textColor}
mb="12px"
>
Upgrade to Unlimited
</Text>
<Text
mb="24px"
fontWeight="500"
fontSize="md"
color="gray.500"
>
Get access to all features and generate
premium and exclusive essays with our
unlimited plan!
</Text>
{/* Features */}
<Flex
w={{ base: '100%', xl: '80%' }}
direction="column"
>
<Flex alignItems="center" mb="20px">
<Icon
me="10px"
w="20px"
h="20px"
color={'green.500'}
as={MdCheckCircle}
/>
<Text
as="span"
fontSize="sm"
fontWeight="600"
color={textColor}
letterSpacing="0px"
>
Access to 12+ Essay types
</Text>
</Flex>
<Flex alignItems="center" mb="20px">
<Icon
me="10px"
w="20px"
h="20px"
color={'green.500'}
as={MdCheckCircle}
/>
<Text
as="span"
fontSize="sm"
fontWeight="600"
color={textColor}
letterSpacing="0px"
>
Up to 1500 words per Essay
</Text>
</Flex>
<Flex alignItems="center" mb="20px">
<Icon
me="10px"
w="20px"
h="20px"
color={'green.500'}
as={MdCheckCircle}
/>
<Text
as="span"
fontSize="sm"
fontWeight="600"
color={textColor}
letterSpacing="0px"
>
Academic Citation formats (APA, etc.)
</Text>
</Flex>
<Flex alignItems="center" mb="20px">
<Icon
me="10px"
w="20px"
h="20px"
color={'green.500'}
as={MdCheckCircle}
/>
<Text
as="span"
fontSize="sm"
fontWeight="600"
color={textColor}
letterSpacing="0px"
>
Academic Levels (Master, etc.)
</Text>
</Flex>
<Flex alignItems="center" mb="30px">
<Icon
me="10px"
w="20px"
h="20px"
color={'green.500'}
as={MdCheckCircle}
/>
<Text
as="span"
fontSize="sm"
fontWeight="600"
color={textColor}
letterSpacing="0px"
>
Essay Tones (Academic, etc.)
</Text>
</Flex>
</Flex>
{/* YEARLY */}
<Flex
onClick={() =>
setPlan({
product: 'prod_PtTJ6R3RnzmIPX',
price: 'price_1P3gMyGx8VbJPRgzkoB6Fp8F'
})
}
transition="0.15s linear"
align="center"
position="relative"
border="1px solid"
borderColor={
plan.product === 'prod_PtTJ6R3RnzmIPX'
? 'brand.500'
: borderColor
}
borderRadius="10px"
w="100%"
py="14px"
px="14px"
cursor="pointer"
mb="20px"
>
<Text
fontSize="sm"
fontWeight={'700'}
color="#120F43"
mb="2x"
ms="8px"
me="8px"
>
Yearly
</Text>
<Badge
display={{
base: 'flex',
lg: 'none',
xl: 'flex'
}}
colorScheme="green"
borderRadius="4px"
color="green.500"
textTransform={'none'}
letterSpacing="0px"
px="0px"
w="max-content"
>
Save 35%
</Badge>
<Text
display="flex"
ms="auto"
fontSize="md"
color={textColor}
letterSpacing="0px"
fontWeight="600"
lineHeight="100%"
>
$5.75
<Text
fontSize={'14px'}
color="gray.500"
fontWeight="500"
ms="4px"
as="span"
>
/month
</Text>
</Text>
</Flex>
{/* END YEARLY */}
{/* MONTHLY */}
<Flex
onClick={() =>
setPlan({
product: 'prod_PtTCPDFZbburMa',
price: 'price_1P3gGXGx8VbJPRgzdEZODy8K'
})
}
transition="0.15s linear"
align="center"
position="relative"
border="1px solid"
borderColor={
plan.product === 'prod_PtTCPDFZbburMa'
? 'brand.500'
: borderColor
}
borderRadius="10px"
w="100%"
py="16px"
px="14px"
cursor="pointer"
mb="28px"
>
<Text
fontSize="sm"
fontWeight={'700'}
color="#120F43"
mb="2x"
ms="8px"
me="4px"
>
Monthly
</Text>
<Text
display="flex"
ms="auto"
fontSize="md"
color={textColor}
letterSpacing="0px"
fontWeight="600"
lineHeight="100%"
>
$9
<Text
fontSize={'14px'}
color="gray.500"
fontWeight="500"
ms="4px"
as="span"
>
/month
</Text>
</Text>
</Flex>
{/* END MONTHLY */}
{products.map((product: any) => {
const price = product?.prices?.find(
(price: any) => price.id === plan.price
);
if (product.id === plan.product) {
if (!price) return null;
return (
<Button
py="20px"
px="16px"
fontSize="sm"
variant="primary"
borderRadius="45px"
w={{ base: '100%' }}
h="54px"
mb="28px"
_hover={{
boxShadow:
'0px 21px 27px -10px rgba(96, 60, 255, 0.48) !important',
bg:
'linear-gradient(15.46deg, #4A25E1 26.3%, #7B5AFF 86.4%) !important',
_disabled: {
bg:
'linear-gradient(15.46deg, #4A25E1 26.3%, #7B5AFF 86.4%)'
}
}}
onClick={() => handleCheckout(price)}
>
Upgrade now
<Icon
as={MdChevronRight}
mt="2px"
h="16px"
w="16px"
/>
</Button>
);
}
})}
<Text
fontSize="xs"
color="gray.500"
fontWeight={'500'}
mx="auto"
mb="5px"
>
Used by 80,000+ users monthly
</Text>
<Flex
direction="row"
alignItems="center"
mx="auto"
>
<Icon
me="1px"
w="16px"
h="16px"
color="orange.500"
as={IoIosStar}
/>
<Icon
me="1px"
w="16px"
h="16px"
color="orange.500"
as={IoIosStar}
/>
<Icon
me="1px"
w="16px"
h="16px"
color="orange.500"
as={IoIosStar}
/>
<Icon
me="1px"
w="16px"
h="16px"
color="orange.500"
as={IoIosStar}
/>
<Icon
me="6px"
w="16px"
h="16px"
color="orange.500"
as={IoIosStar}
/>
<Text
fontSize="sm"
fontWeight="800"
h="100%"
color={textColor}
>
4.9
</Text>
</Flex>
</Flex>
</Flex>
</ModalBody>
<ModalCloseButton
borderRadius="full"
color="#120F43"
bg="#F4F6FB !important"
_hover={{ bg: '#E9EDF6 !important' }}
_focus={{ bg: '#F4F6FB !important' }}
_active={{ bg: '#F4F6FB !important' }}
zIndex="99"
/>
</ModalContent>
</Modal>
<Flex
w="100%"
cursor={'pointer'}
onClick={() => onOpen()}
>
<Flex
w="100%"
alignItems="center"
justifyContent="center"
>
<Box
color={
activeRoute(route.path.toLowerCase())
? activeIcon
: inactiveColor
}
me="12px"
mt="6px"
>
{route.icon}
</Box>
<Text
me="auto"
color={
activeRoute(route.path.toLowerCase())
? activeColor
: 'gray.500'
}
fontWeight={
activeRoute(route.path.toLowerCase())
? '700'
: '500'
}
letterSpacing="0px"
fontSize="sm"
>
{route.name}
</Text>
{route.rightElement ? (
<Flex
border="1px solid"
borderColor={borderColor}
borderRadius="full"
w="34px"
h="34px"
justify={'center'}
align="center"
color={iconColor}
ms="auto"
me="10px"
>
<Icon
as={IoMdAdd}
width="20px"
height="20px"
color="inherit"
/>
</Flex>
) : null}
<Badge
display={{ base: 'flex', lg: 'none', xl: 'flex' }}
colorScheme="brand"
borderRadius="25px"
color="brand.500"
textTransform={'none'}
letterSpacing="0px"
px="8px"
>
PRO
</Badge>
</Flex>
</Flex>
</Flex>
) : (
<NavLink
href={
route.layout ? route.layout + route.path : route.path
}
key={key}
styles={{ width: '100%' }}
>
<Flex
w="100%"
alignItems="center"
justifyContent="center"
>
<Box
color={
activeRoute(route.path.toLowerCase())
? activeIcon
: inactiveColor
}
me="12px"
mt="6px"
>
{route.icon}
</Box>
<Text
me="auto"
color={
activeRoute(route.path.toLowerCase())
? activeColor
: 'gray.500'
}
fontWeight={
activeRoute(route.path.toLowerCase())
? '700'
: '500'
}
letterSpacing="0px"
fontSize="sm"
>
{route.name}
</Text>
{route.rightElement ? (
<Flex
border="1px solid"
borderColor={borderColor}
borderRadius="full"
w="34px"
h="34px"
justify={'center'}
align="center"
color={iconColor}
ms="auto"
me="10px"
>
<Icon
as={IoMdAdd}
width="20px"
height="20px"
color="inherit"
/>
</Flex>
) : null}
</Flex>
</NavLink>
)}
</HStack>
</Flex>
) : (
<ListItem ms={0}>
<Flex ps="32px" alignItems="center" mb="8px">
<NavLink
href={route.layout ? route.layout + route.path : route.path}
key={key}
>
<Text
color={
activeRoute(route.path.toLowerCase())
? activeColor
: inactiveColor
}
fontWeight="500"
fontSize="xs"
>
{route.name}
</Text>
</NavLink>
</Flex>
</ListItem>
)}
</Box>
);
}
});
};
// this function creates the links from the secondary accordions (for example auth -> sign-in -> default)
const createAccordionLinks = (routes: IRoute[]) => {
return routes.map((route: IRoute, key: number) => {
return (
<ListItem
ms="28px"
display="flex"
alignItems="center"
mb="10px"
key={key}
>
<NavLink href={route.layout + route.path} key={key}>
<Icon w="6px" h="6px" me="8px" as={FaCircle} color={activeIcon} />
<Text
color={
activeRoute(route.path.toLowerCase())
? activeColor
: inactiveColor
}
fontWeight={
activeRoute(route.path.toLowerCase()) ? 'bold' : 'normal'
}
fontSize="sm"
>
{route.name}
</Text>
</NavLink>
</ListItem>
);
});
};
// BRAND
return <>{createLinks(routes)}</>;
}
export default SidebarLinks;

View File

@@ -0,0 +1,556 @@
'use client';
import {
ProductsContext,
SubscriptionContext,
UserContext
} from '@/contexts/layout';
import modalImage from '@/public/Modal.png';
import SidebarImage from '@/public/SidebarBadge.png';
import { Database } from '@/types_db';
import { getErrorRedirect } from '@/utils/helpers';
import { getStripe } from '@/utils/stripe/client';
import { checkoutWithStripe } from '@/utils/stripe/server';
import {
Badge,
Button,
Flex,
Icon,
Image,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalOverlay,
Text,
useColorModeValue,
useDisclosure
} from '@chakra-ui/react';
import { usePathname, useRouter } from 'next/navigation';
import { useContext, useState } from 'react';
import { IoIosStar } from 'react-icons/io';
import { MdCheckCircle, MdChevronRight } from 'react-icons/md';
type Price = Database['public']['Tables']['prices']['Row'];
interface SidebarCard {
[x: string]: any;
}
export default function SidebarCard(props: SidebarCard) {
const [priceIdLoading, setPriceIdLoading] = useState<string>();
const textColor = useColorModeValue('#120F43', 'white');
const currentPath = usePathname();
const products = useContext(ProductsContext);
const subscription = useContext(SubscriptionContext);
const { isOpen, onOpen, onClose } = useDisclosure();
const [plan, setPlan] = useState({
product: 'prod_PtTCPDFZbburMa',
price: 'price_1P3gGXGx8VbJPRgzdEZODy8K'
});
const borderColor = 'secondaryGray.200';
const router = useRouter();
const handleCheckout = async (price: Price) => {
setPriceIdLoading(price.id);
const user = useContext(UserContext);
if (!user) {
setPriceIdLoading(undefined);
return router.push('/dashboard/signin/signup');
}
const { errorRedirect, sessionId } = await checkoutWithStripe(
price,
currentPath
);
if (errorRedirect) {
setPriceIdLoading(undefined);
return router.push(errorRedirect);
}
if (!sessionId) {
setPriceIdLoading(undefined);
return router.push(
getErrorRedirect(
currentPath,
'An unknown error occurred.',
'Please try again later or contact a system administrator.'
)
);
}
const stripe = await getStripe();
stripe?.redirectToCheckout({ sessionId });
setPriceIdLoading(undefined);
};
if (subscription) {
// -------------- PRO User Card --------------
return (
<Flex
align="center"
position="relative"
border="1px solid"
borderColor={borderColor}
borderRadius="16px"
w="100%"
py="16px"
px="14px"
>
<Image alt=" " src={SidebarImage.src} maxW="27px" me="10px" />
<Flex direction="column" justify="center" w="100%">
<Text fontSize="sm" fontWeight={'700'} color="#120F43" mb="2x">
PRO Member
</Text>
<Text fontWeight={'500'} fontSize="sm" color="gray.500">
Unlimited plan active
</Text>
</Flex>
</Flex>
);
} else {
// -------------- Free User Card --------------
return (
<Flex
justify="center"
direction="column"
align="center"
position="relative"
border="1px solid"
borderColor={borderColor}
borderRadius="16px"
w="100%"
py="20px"
px="18px"
>
<Image alt=" " src={SidebarImage.src} maxW="54px" />
<Flex direction="column" mb="12px" w="100%" pt="16px">
<Text
fontSize="lg"
fontWeight={'700'}
color="#120F43"
mb="10px"
textAlign={'center'}
>
Upgrade to Unlimited
</Text>
<Text
textAlign={'center'}
fontWeight={'500'}
fontSize="sm"
color="gray.500"
mb="14px"
>
Generate premium Essays by upgrading to an unlimited plan!
</Text>
</Flex>
<Button
onClick={() => {
onOpen();
}}
py="20px"
px="16px"
fontSize="sm"
variant="primary"
borderRadius="45px"
w={{ base: '100%' }}
h="54px"
mb="14px"
_hover={{
boxShadow: '0px 21px 27px -10px rgba(96, 60, 255, 0.48) !important',
bg:
'linear-gradient(15.46deg, #4A25E1 26.3%, #7B5AFF 86.4%) !important',
_disabled: {
bg: 'linear-gradient(15.46deg, #4A25E1 26.3%, #7B5AFF 86.4%)'
}
}}
>
Go unlimited for just $9
</Button>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay bg="rgba(0, 0, 0, 0.85)" />
<ModalContent
mx="8px"
bg="transparent"
boxShadow="unset"
maxW="unset"
w="unset"
>
<ModalBody p="0px" position={'relative'}>
<Flex>
<Image
display={{ base: 'none', md: 'block' }}
zIndex="98"
borderLeftRadius="16px"
src={modalImage.src}
w="340px"
alt=" "
/>
<Flex
bg="white"
borderLeftRadius={{ base: '16px', md: '0px' }}
borderRightRadius="16px"
direction={'column'}
px={{ base: '30px', md: '42px' }}
py="34px"
w={{ md: '412px', lg: '456px' }}
minW={{ md: '412px', lg: '456px' }}
>
<Text
fontSize="26px"
fontWeight={'800'}
color={textColor}
mb="12px"
>
Upgrade to Unlimited
</Text>
<Text
mb="24px"
fontWeight="500"
fontSize="md"
color="gray.500"
>
Get access to all features and generate premium and
exclusive essays with our unlimited plan!
</Text>
{/* Features */}
<Flex w={{ base: '100%', xl: '80%' }} direction="column">
<Flex alignItems="center" mb="20px">
<Icon
me="10px"
w="20px"
h="20px"
color={'green.500'}
as={MdCheckCircle}
/>
<Text
as="span"
fontSize="sm"
fontWeight="600"
color={textColor}
letterSpacing="0px"
>
Access to 12+ Essay types
</Text>
</Flex>
<Flex alignItems="center" mb="20px">
<Icon
me="10px"
w="20px"
h="20px"
color={'green.500'}
as={MdCheckCircle}
/>
<Text
as="span"
fontSize="sm"
fontWeight="600"
color={textColor}
letterSpacing="0px"
>
Up to 1500 words per Essay
</Text>
</Flex>
<Flex alignItems="center" mb="20px">
<Icon
me="10px"
w="20px"
h="20px"
color={'green.500'}
as={MdCheckCircle}
/>
<Text
as="span"
fontSize="sm"
fontWeight="600"
color={textColor}
letterSpacing="0px"
>
Academic Citation formats (APA, etc.)
</Text>
</Flex>
<Flex alignItems="center" mb="20px">
<Icon
me="10px"
w="20px"
h="20px"
color={'green.500'}
as={MdCheckCircle}
/>
<Text
as="span"
fontSize="sm"
fontWeight="600"
color={textColor}
letterSpacing="0px"
>
Academic Levels (Master, etc.)
</Text>
</Flex>
<Flex alignItems="center" mb="30px">
<Icon
me="10px"
w="20px"
h="20px"
color={'green.500'}
as={MdCheckCircle}
/>
<Text
as="span"
fontSize="sm"
fontWeight="600"
color={textColor}
letterSpacing="0px"
>
Essay Tones (Academic, etc.)
</Text>
</Flex>
</Flex>
{/* YEARLY */}
<Flex
onClick={() =>
setPlan({
product: 'prod_PtTJ6R3RnzmIPX',
price: 'price_1P3gMyGx8VbJPRgzkoB6Fp8F'
})
}
transition="0.15s linear"
align="center"
position="relative"
border="1px solid"
borderColor={
plan.product === 'prod_PtTJ6R3RnzmIPX'
? 'brand.500'
: borderColor
}
borderRadius="10px"
w="100%"
py="14px"
px="14px"
cursor="pointer"
mb="20px"
>
<Text
fontSize="sm"
fontWeight={'700'}
color="#120F43"
mb="2x"
ms="8px"
me="8px"
>
Yearly
</Text>
<Badge
display={{
base: 'flex',
lg: 'none',
xl: 'flex'
}}
colorScheme="green"
borderRadius="4px"
color="green.500"
textTransform={'none'}
letterSpacing="0px"
px="0px"
w="max-content"
>
Save 35%
</Badge>
<Text
display="flex"
ms="auto"
fontSize="md"
color={textColor}
letterSpacing="0px"
fontWeight="600"
lineHeight="100%"
>
$5.75
<Text
fontSize={'14px'}
color="gray.500"
fontWeight="500"
ms="4px"
as="span"
>
/month
</Text>
</Text>
</Flex>
{/* END YEARLY */}
{/* MONTHLY */}
<Flex
onClick={() =>
setPlan({
product: 'prod_PtTCPDFZbburMa',
price: 'price_1P3gGXGx8VbJPRgzdEZODy8K'
})
}
transition="0.15s linear"
align="center"
position="relative"
border="1px solid"
borderColor={
plan.product === 'prod_PtTCPDFZbburMa'
? 'brand.500'
: borderColor
}
borderRadius="10px"
w="100%"
py="16px"
px="14px"
cursor="pointer"
mb="28px"
>
<Text
fontSize="sm"
fontWeight={'700'}
color="#120F43"
mb="2x"
ms="8px"
me="4px"
>
Monthly
</Text>
<Text
display="flex"
ms="auto"
fontSize="md"
color={textColor}
letterSpacing="0px"
fontWeight="600"
lineHeight="100%"
>
$9
<Text
fontSize={'14px'}
color="gray.500"
fontWeight="500"
ms="4px"
as="span"
>
/month
</Text>
</Text>
</Flex>
{/* END MONTHLY */}
{products.map((product: any) => {
const price = product?.prices?.find(
(price: any) => price.id === plan.price
);
if (product.id === plan.product) {
if (!price) return null;
return (
<Button
key={product.id}
py="20px"
px="16px"
fontSize="sm"
variant="primary"
borderRadius="45px"
w={{ base: '100%' }}
h="54px"
mb="28px"
_hover={{
boxShadow:
'0px 21px 27px -10px rgba(96, 60, 255, 0.48) !important',
bg:
'linear-gradient(15.46deg, #4A25E1 26.3%, #7B5AFF 86.4%) !important',
_disabled: {
bg:
'linear-gradient(15.46deg, #4A25E1 26.3%, #7B5AFF 86.4%)'
}
}}
onClick={() => handleCheckout(price)}
>
Upgrade now
<Icon
as={MdChevronRight}
mt="2px"
h="16px"
w="16px"
/>
</Button>
);
}
})}
<Text
fontSize="xs"
color="gray.500"
fontWeight={'500'}
mx="auto"
mb="5px"
>
Used by 80,000+ users monthly
</Text>
<Flex direction="row" alignItems="center" mx="auto">
<Icon
me="1px"
w="16px"
h="16px"
color="orange.500"
as={IoIosStar}
/>
<Icon
me="1px"
w="16px"
h="16px"
color="orange.500"
as={IoIosStar}
/>
<Icon
me="1px"
w="16px"
h="16px"
color="orange.500"
as={IoIosStar}
/>
<Icon
me="1px"
w="16px"
h="16px"
color="orange.500"
as={IoIosStar}
/>
<Icon
me="6px"
w="16px"
h="16px"
color="orange.500"
as={IoIosStar}
/>
<Text
fontSize="sm"
fontWeight="800"
h="100%"
color={textColor}
>
4.9
</Text>
</Flex>
</Flex>
</Flex>
</ModalBody>
<ModalCloseButton
borderRadius="full"
color="#120F43"
bg="#F4F6FB !important"
_hover={{ bg: '#E9EDF6 !important' }}
_focus={{ bg: '#F4F6FB !important' }}
_active={{ bg: '#F4F6FB !important' }}
zIndex="99"
/>
</ModalContent>
</Modal>
<Text
textAlign={'center'}
fontWeight={'500'}
fontSize="xs"
color="gray.500"
>
Join 80,000+ users now
</Text>
</Flex>
);
}
}