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,912 @@
/*eslint-disable*/
'use client';
import MessageBox from '@/components/MessageBox';
import Card from '@/components/card/Card';
import DashboardLayout from '@/components/layout';
import modalImage from '@/public/Modal.png';
import { EssayBody, OpenAIModel } from '@/types/types';
import { Database } from '@/types_db';
import { getErrorRedirect } from '@/utils/helpers';
import { getStripe } from '@/utils/stripe/client';
import {
Badge,
Button,
Flex,
FormLabel,
Icon,
Image,
Link,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalOverlay,
Select,
Text,
Textarea,
useColorModeValue,
useDisclosure,
useToast
} from '@chakra-ui/react';
import { User } from '@supabase/supabase-js';
import { usePathname, useRouter } from 'next/navigation';
import { useState } from 'react';
import { IoIosStar } from 'react-icons/io';
import {
MdCheckCircle,
MdChevronRight,
MdOutlineWorkspacePremium
} from 'react-icons/md';
import { checkoutWithStripe } from '@/utils/stripe/server';
type Subscription = Database['public']['Tables']['subscriptions']['Row'];
type Product = Database['public']['Tables']['products']['Row'];
type Price = Database['public']['Tables']['prices']['Row'];
interface ProductWithPrices extends Product {
prices: Price[];
}
interface PriceWithProduct extends Price {
products: Product | null;
}
interface SubscriptionWithProduct extends Subscription {
prices: PriceWithProduct | null;
}
interface Props {
user: User | null | undefined;
products: ProductWithPrices[];
subscription: SubscriptionWithProduct | null;
userDetails: { [x: string]: any } | null;
}
export default function AiGenerator(props: Props) {
const [priceIdLoading, setPriceIdLoading] = useState<string>();
const [plan, setPlan] = useState({
product: 'prod_PtTCPDFZbburMa',
price: 'price_1P3gGXGx8VbJPRgzdEZODy8K'
});
const router = useRouter();
const currentPath = usePathname();
const handleCheckout = async (price: Price) => {
setPriceIdLoading(price.id);
if (!props.user) {
setPriceIdLoading(undefined);
return router.push('/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);
};
// Input States
const { isOpen, onOpen, onClose } = useDisclosure();
const [words, setWords] = useState<'300' | '200'>('200');
const [essayType, setEssayType] = useState<
'' | 'Argumentative' | 'Classic' | 'Persuasive' | 'Critique'
>('');
const [topic, setTopic] = useState<string>('');
// Response message
const [outputCode, setOutputCode] = useState<string>('');
// ChatGPT model
const [model, setModel] = useState<OpenAIModel>('gpt-3.5-turbo');
// Loading state
const [loading, setLoading] = useState<boolean>(false);
// API Key
// const [apiKey, setApiKey] = useState<string>();
const textColor = useColorModeValue('#120F43', 'white');
const placeholderColor = useColorModeValue(
{ color: 'gray.500' },
{ color: 'whiteAlpha.600' }
);
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.200');
const toast = useToast();
// -------------- Main API Handler --------------
const handleTranslate = async () => {
const maxCodeLength = model === 'gpt-3.5-turbo' ? 700 : 700;
// Chat post conditions(maximum number of characters, valid message etc.)
// if (!apiKey?.includes('sk-') && !apiKey?.includes('sk-')) {
// alert('Please enter an API key.');
// return;
// }
if (!topic) {
alert('Please enter your subject.');
return;
}
if (!words) {
alert('Please choose number of words.');
return;
}
if (!essayType) {
alert('Please choose a type of essay.');
return;
}
if (topic.length > maxCodeLength) {
alert(
`Please enter code less than ${maxCodeLength} characters. You are currently at ${topic.length} characters.`
);
return;
}
setLoading(true);
setOutputCode('');
const controller = new AbortController();
const body: EssayBody = {
topic,
words,
essayType,
model
};
// -------------- Fetch --------------
const response = await fetch('/api/essayAPI', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
signal: controller.signal,
body: JSON.stringify(body)
});
if (!response.ok) {
setLoading(false);
if (response) {
alert(
'Something went wrong went fetching from the API. Make sure to use a valid API key.'
);
}
return;
}
const data = response.body;
if (!data) {
setLoading(false);
alert('Something went wrong');
return;
}
const reader = data.getReader();
const decoder = new TextDecoder();
let done = false;
let code = '';
while (!done) {
const { value, done: doneReading } = await reader.read();
done = doneReading;
const chunkValue = decoder.decode(value);
code += chunkValue;
setOutputCode((prevCode) => prevCode + chunkValue);
}
setLoading(false);
copyToClipboard(code);
};
// -------------- Copy Response --------------
const copyToClipboard = (text: string) => {
const el = document.createElement('textarea');
el.value = text;
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
};
// *** Initializing apiKey with .env.local value
// useEffect(() => {
// ENV file verison
// const apiKeyENV = process.env.NEXT_PUBLIC_OPENAI_API_KEY;
// if (apiKey === undefined || null) {
// setApiKey(apiKeyENV);
// }
// }, []);
// -------------- Input Value Handler --------------
const handleChange = (Event: any) => {
setTopic(Event.target.value);
};
const handleChangeParagraphs = (Event: any) => {
setWords(Event.target.value);
};
const handleChangeEssayType = (Event: any) => {
setEssayType(Event.target.value);
};
return (
<DashboardLayout
userDetails={props.userDetails}
user={props?.user}
products={props.products}
subscription={props.subscription}
title="Essay Generator"
description="Essay Generator"
>
<Flex
w="100%"
direction="column"
position="relative"
mt={{ base: '70px', md: '0px', xl: '0px' }}
>
<Flex
mx="auto"
w={{ base: '100%', md: '100%', xl: '100%' }}
maxW="100%"
justify="center"
direction={{ base: 'column', md: 'row' }}
>
<Card
minW={{ base: '100%', md: '40%', xl: '476px' }}
maxW={{ base: '100%', md: '40%', xl: '476px' }}
h="min-content"
me={{ base: '0px', md: '20px' }}
mb={{ base: '20px', md: '0px' }}
>
<Text
fontSize={'30px'}
color={textColor}
fontWeight="800"
mb="10px"
>
Essay Topic
</Text>
<Text fontSize="md" color="gray.500" fontWeight="500" mb="30px">
What your essay will be about?
</Text>
<Textarea
border="1px solid"
borderRadius={'10px'}
borderColor={borderColor}
p="15px 20px"
mb="28px"
minH="224px"
fontWeight="500"
_focus={{ borderColor: 'none' }}
color={textColor}
placeholder="Type here your topic..."
_placeholder={placeholderColor}
onChange={handleChange}
/>
<FormLabel
display="flex"
ms="10px"
htmlFor={'parag'}
fontSize="md"
color={textColor}
letterSpacing="0px"
fontWeight="bold"
_hover={{ cursor: 'pointer' }}
>
Number of words
</FormLabel>
<Select
border="1px solid"
borderRadius={'10px'}
borderColor={borderColor}
h="60px"
id="type"
placeholder="Select option"
_focus={{ borderColor: 'none' }}
mb="28px"
onChange={handleChangeParagraphs}
>
<option value={'200'}>200</option>
<option value={'300'}>300</option>
</Select>
<FormLabel
display="flex"
ms="10px"
htmlFor={'type'}
fontSize="md"
color={textColor}
letterSpacing="0px"
fontWeight="bold"
_hover={{ cursor: 'pointer' }}
>
Select your Essay type
</FormLabel>
<Select
border="1px solid"
borderRadius={'10px'}
borderColor={borderColor}
h="60px"
id="type"
placeholder="Select option"
_focus={{ borderColor: 'none' }}
mb="28px"
onChange={handleChangeEssayType}
>
<option value="Argumentative">Argumentative</option>
<option value="Classic">Classic</option>
<option value="Persuasive">Persuasive</option>
<option value="Critique">Critique</option>
</Select>{' '}
{props.subscription ? (
<Flex direction="column">
<Text
ms="10px"
fontSize="md"
color={textColor}
letterSpacing="0px"
fontWeight="bold"
mb="12px"
>
Looking for all features?
</Text>
<Link href="/dashboard/premium-essays">
<Flex
border="1px solid"
borderRadius={'14px'}
borderColor={borderColor}
py="14px"
mb="28px"
align="center"
px="16px"
>
<Flex
border="1px solid"
borderRadius="99px"
borderColor="secondaryGray.200"
p="10px"
h="max-content"
>
<Icon
color="brand.500"
as={MdOutlineWorkspacePremium}
h="20px"
w="20px"
/>
</Flex>
<Text
ms="10px"
fontSize="md"
color={textColor}
letterSpacing="0px"
fontWeight="bold"
>
Try our Premium Essay Generator
</Text>
<Icon
color={textColor}
as={MdChevronRight}
h="20px"
w="20px"
ms="auto"
mb="-2px"
me="4px"
/>
</Flex>
</Link>
</Flex>
) : (
<Flex direction="column">
<Text
ms="10px"
fontSize="md"
color={textColor}
letterSpacing="0px"
fontWeight="bold"
mb="12px"
>
Looking for more features?
</Text>
<Flex
cursor="pointer"
onClick={() => {
onOpen();
}}
border="1px solid"
borderRadius={'14px'}
borderColor={borderColor}
py="14px"
mb="28px"
align="center"
px="16px"
>
<Flex
border="1px solid"
borderRadius="99px"
borderColor="secondaryGray.200"
p="10px"
h="max-content"
>
<Icon
color="brand.500"
as={MdOutlineWorkspacePremium}
h="20px"
w="20px"
/>
</Flex>
<Text
ms="10px"
fontSize="md"
color={textColor}
letterSpacing="0px"
fontWeight="bold"
>
Try our Premium Essay Generator
</Text>
<Icon
color={textColor}
as={MdChevronRight}
h="20px"
w="20px"
ms="auto"
mb="-2px"
me="4px"
/>
</Flex>
</Flex>
)}
<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 */}
{props.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>
<Button
py="20px"
px="16px"
fontSize="md"
variant="primary"
borderRadius="45px"
w={{ base: '100%' }}
h="54px"
onClick={handleTranslate}
isLoading={loading ? true : false}
_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%)'
}
}}
>
Generate your Essay
</Button>
</Card>
<Card maxW="100%" h="100%">
<Text
fontSize={'30px'}
color={textColor}
fontWeight="800"
mb="10px"
>
AI Output
</Text>
<Text fontSize="md" color="gray.500" fontWeight="500" mb="30px">
Enjoy your outstanding essay!
</Text>
<MessageBox output={outputCode} />
<Button
variant="transparent"
border="1px solid"
borderColor={borderColor}
borderRadius="full"
maxW="160px"
ms="auto"
fontSize="md"
w={{ base: '300px', md: '420px' }}
h="54px"
onClick={() => {
if (outputCode) navigator.clipboard.writeText(outputCode);
toast({
title: outputCode
? `Essay succesfully copied!`
: `Generate an essay first!`,
position: 'top',
status: outputCode ? 'success' : `error`,
isClosable: true
});
}}
>
Copy text
</Button>
</Card>
</Flex>
</Flex>
</DashboardLayout>
);
}