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,457 @@
'use client';
/*eslint-disable*/
import MessageBoxChat from '@/components/MessageBoxChat';
import DashboardLayout from '@/components/layout';
import Bg from '@/public/img/ai-chat/bg-image.png';
import { Database } from '@/types_db';
import {
Button,
Flex,
Icon,
Image,
Input,
Text,
useColorModeValue
} from '@chakra-ui/react';
import { User } from '@supabase/supabase-js';
import endent from 'endent';
import { useState } from 'react';
import { MdAutoAwesome, MdEdit, MdPerson } from 'react-icons/md';
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 AiAssistant(props: Props) {
// *** If you use .env.local variable for your API key, method which we recommend, use the apiKey variable commented below
// Input States
const [inputMessage, setInputMessage] = useState<string>('');
const [submitMessage, setSubmitMessage] = useState<string>('');
// Loading state
const [loading, setLoading] = useState<boolean>(false);
const [assistant, setAssistant] = useState(Object);
const [thread, setThread] = useState(Object);
const [res_message, setResMessage] = useState(Object);
const gray = useColorModeValue('gray.500', 'white');
const brandColor = useColorModeValue('brand.500', 'white');
const gradientBg2 = useColorModeValue(
'linear-gradient(15.46deg, #4A25E1 26.3%, #7B5AFF 86.4%)',
'linear-gradient(180deg, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0.31) 100%)'
);
const textColor = useColorModeValue('#120F43', 'white');
const placeholderColor = useColorModeValue(
{ color: 'gray.500' },
{ color: 'whiteAlpha.600' }
);
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.200');
const createPrompt = (inputMessage: string) => {
const data = (inputMessage: string) => {
return endent` do me this:
${inputMessage}
`;
};
if (inputMessage) {
return data(inputMessage);
}
};
const getAssistant = async () => {
const gptResponse = await fetch(
'https://api.openai.com/v1/assistants/' +
process.env.NEXT_PUBLIC_OPENAI_ASSISTANT_KEY,
{
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NEXT_PUBLIC_OPENAI_API_KEY}`,
'Content-Type': 'application/json',
'OpenAI-Beta': 'assistants=v1'
}
}
);
const assistant = await gptResponse.json();
return assistant;
};
const createThread = async () => {
const gptResponse = await fetch('https://api.openai.com/v1/threads', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NEXT_PUBLIC_OPENAI_API_KEY}`,
'Content-Type': 'application/json',
'OpenAI-Beta': 'assistants=v1'
}
});
const thread = await gptResponse.json();
return thread;
};
const createMessage = async (thread_id: string) => {
const prompt = createPrompt(inputMessage);
const gptResponse = await fetch(
'https://api.openai.com/v1/threads/' + thread_id + '/messages',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NEXT_PUBLIC_OPENAI_API_KEY}`,
'Content-Type': 'application/json',
'OpenAI-Beta': 'assistants=v1'
},
body: JSON.stringify({
role: 'user',
// content: topic,
content: prompt
})
}
);
const message = await gptResponse.json();
return message;
};
const getMessage = async (thread_id: string, message_id: string) => {
// https://api.openai.com/v1/threads/{thread_id}/messages/{message_id}
const gptResponse = await fetch(
'https://api.openai.com/v1/threads/' + thread_id + '/messages',
{
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NEXT_PUBLIC_OPENAI_API_KEY}`,
'Content-Type': 'application/json',
'OpenAI-Beta': 'assistants=v1'
}
}
);
const message = await gptResponse.json();
console.log('I get the message.');
console.log(message);
return message;
};
const runAssistant = async (thread_id: string, assistant_id: string) => {
const gptResponse = await fetch(
'https://api.openai.com/v1/threads/' + thread_id + '/runs',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.NEXT_PUBLIC_OPENAI_API_KEY}`,
'Content-Type': 'application/json',
'OpenAI-Beta': 'assistants=v1'
},
body: JSON.stringify({
assistant_id: assistant_id
})
}
);
const run_res = await gptResponse.json();
return run_res;
};
const getRunAssistant = async (run_id: string, thread_id: string) => {
const gptResponse = await fetch(
'https://api.openai.com/v1/threads/' + thread_id + '/runs/' + run_id,
{
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.NEXT_PUBLIC_OPENAI_API_KEY}`,
'Content-Type': 'application/json',
'OpenAI-Beta': 'assistants=v1'
}
}
);
const run_res = await gptResponse.json();
console.log('I get the status.');
console.log(run_res);
return run_res;
};
const deleteThread = async (thread_id: string) => {
if (thread === undefined) {
return;
}
const gptResponse = await fetch(
'https://api.openai.com/v1/threads/' + thread_id,
{
method: 'DELETE',
headers: {
Authorization: `Bearer ${process.env.NEXT_PUBLIC_OPENAI_API_KEY}`,
'Content-Type': 'application/json',
'OpenAI-Beta': 'assistants=v1'
}
}
);
const thread_res = await gptResponse.json();
console.log(thread_res);
return thread_res;
};
const handleSubmit = async (e: any) => {
e.preventDefault();
setLoading(true);
// save the keys in storage browser
// @ts-ignore
localStorage.setItem('open_ai_key', process.env.NEXT_PUBLIC_OPENAI_API_KEY);
localStorage.setItem(
'assistant_key',
// @ts-ignore
process.env.NEXT_PUBLIC_OPENAI_ASSISTANT_KEY
);
const assistant_res = await getAssistant();
setAssistant(assistant_res);
const thread_res = await createThread();
setThread(thread_res);
const message = await createMessage(thread_res.id);
let runAssistantResponse = await runAssistant(
thread_res.id,
assistant_res.id
);
console.log(runAssistantResponse);
while (runAssistantResponse.status !== 'completed') {
runAssistantResponse = await getRunAssistant(
runAssistantResponse.id,
thread_res.id
);
if (runAssistantResponse.status === 'completed') {
console.log('Message is : ');
const call_response = await getMessage(thread_res.id, message.id);
setResMessage(call_response);
console.log(await deleteThread(thread_res.id));
} else {
// sleep for 2 second
await new Promise((r) => setTimeout(r, 2000));
}
}
console.log(assistant);
console.log(thread);
console.log(message);
console.log(runAssistantResponse);
setSubmitMessage(inputMessage);
setLoading(false);
};
// -------------- 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);
// };
const handleChange = (Event: any) => {
setInputMessage(Event.target.value);
};
return (
<DashboardLayout
userDetails={props.userDetails}
user={props?.user}
products={props.products}
subscription={props.subscription}
title="AI Generator"
description="AI Generator"
>
<Flex
position="relative"
w="100%"
direction="column"
pt={{ base: '20px', md: 0 }}
>
<Image
width={{ base: '340px', xl: '350px' }}
src={Bg.src}
position="absolute"
left={{ base: '-20%', md: '35%', lg: '38%' }}
top={{ base: '50%' }}
zIndex="0"
w="200px"
transform="translate(0, -50%)"
alt=" "
/>
<Flex
direction={'column'}
mx="auto"
minH={{ base: '75vh', xl: '85vh' }}
w="full"
maxW="1000px"
>
{/* Model Change */}
<Flex
w="100%"
direction={'column'}
mb={
res_message?.data?.[0]?.content?.[0].text?.value ? '20px' : 'auto'
}
>
<Text
textAlign={'center'}
fontSize="sm"
fontWeight={'500'}
color="secondaryGray.500"
>
Please make sure that you have set the environmental variable for
the Assistant Key.
</Text>
</Flex>
{/* Main Box */}
<Flex
mx="auto"
w="100%"
direction={'column'}
display={
res_message?.data?.[0]?.content?.[0].text?.value ? 'flex' : 'none'
}
mb="auto"
>
<Flex
mb="10px"
display={'flex'}
w="100%"
alignItems={'center'}
justifyContent="center"
>
<Flex
me="20px"
h="40px"
minH="40px"
minW="40px"
align="center"
borderRadius="full"
border="1px solid"
borderColor={borderColor}
>
<Icon as={MdPerson} color={brandColor} h="20px" w="20px" />
</Flex>
<Flex
zIndex={2}
borderRadius="14px"
w="100%"
border="1px solid"
borderColor={borderColor}
p="20px"
backdropFilter="blur(24px)"
>
<Text
color={textColor}
fontSize={{ base: 'sm', md: '16px' }}
fontWeight={'600'}
>
{submitMessage}
</Text>
<Icon
as={MdEdit}
color={'gray.500'}
h="20px"
w="20px"
ms="auto"
cursor={'pointer'}
/>
</Flex>
</Flex>
<Flex w="100%">
<Flex
me="20px"
h="40px"
minW="40px"
align="center"
justify={'center'}
borderRadius="full"
background={gradientBg2}
>
<Icon as={MdAutoAwesome} color={'white'} h="20px" w="20px" />
</Flex>
<MessageBoxChat
output={res_message?.data?.[0]?.content?.[0].text?.value}
/>
</Flex>
</Flex>
{/* Chat Input */}
<Flex mt="20px" justifyContent={'flex-center'} mx="auto">
<Input
color={textColor}
border="1px solid"
borderRadius={'45px'}
borderColor={borderColor}
w={{ md: '100%', xl: '45vw' }}
h="60px"
id="email"
fontSize={'sm'}
fontWeight="500"
placeholder="Type your message here..."
_placeholder={placeholderColor}
_focus={{ borderColor: 'none' }}
mb={{ base: '14px', md: '16px' }}
me="20px"
onChange={handleChange}
/>
<Button
py="20px"
px="16px"
fontSize="sm"
variant="primary"
borderRadius="45px"
h="54px"
_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%)'
}
}}
minW="150px"
onClick={handleSubmit}
isLoading={loading}
>
Submit
</Button>
</Flex>
<Flex
mt="10px"
direction={{ base: 'column', md: 'row' }}
justifyContent="center"
alignItems={'center'}
>
<Text color={gray} textAlign="center" fontSize="xs">
Free Research Preview. ChatGPT may produce inaccurate information
about people, places, or facts. Consider checking important
information.
</Text>
</Flex>
</Flex>
</Flex>
</DashboardLayout>
);
}

View File

@@ -0,0 +1,439 @@
'use client';
/*eslint-disable*/
import MessageBoxChat from '@/components/MessageBoxChat';
import DashboardLayout from '@/components/layout';
import Bg from '@/public/img/ai-chat/bg-image.png';
import { ChatBody } from '@/types/types';
import { Database } from '@/types_db';
import {
Accordion,
AccordionButton,
AccordionIcon,
AccordionItem,
AccordionPanel,
useColorModeValue,
Box,
Icon,
Flex,
Text,
Input,
Button,
Image
} from '@chakra-ui/react';
import { User } from '@supabase/supabase-js';
import { useState } from 'react';
import { MdAutoAwesome, MdBolt, MdEdit, MdPerson } from 'react-icons/md';
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 AiChat(props: Props) {
// *** If you use .env.local variable for your API key, method which we recommend, use the apiKey variable commented below
// Input States
const [inputOnSubmit, setInputOnSubmit] = useState<string>('');
const [inputMessage, setInputMessage] = useState<string>('');
// Response message
const [outputCode, setOutputCode] = useState<string>('');
// ChatGPT model
const [model, setModel] = useState('gpt-3.5-turbo');
// Loading state
const [loading, setLoading] = useState<boolean>(false);
const gray = useColorModeValue('gray.500', 'white');
const brandColor = useColorModeValue('brand.500', 'white');
const opacityBg = useColorModeValue('white', 'whiteAlpha.100');
const gradientBg = useColorModeValue(
'linear-gradient(180deg, #FBFBFF 0%, #CACAFF 100%)',
'linear-gradient(180deg, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0.31) 100%)'
);
const gradientBg2 = useColorModeValue(
'linear-gradient(15.46deg, #4A25E1 26.3%, #7B5AFF 86.4%)',
'linear-gradient(180deg, rgba(255, 255, 255, 0.07) 0%, rgba(255, 255, 255, 0.31) 100%)'
);
const shadow = useColorModeValue(
'14px 27px 45px rgba(112, 144, 176, 0.2)',
'unset'
);
const textColor = useColorModeValue('#120F43', 'white');
const placeholderColor = useColorModeValue(
{ color: 'gray.500' },
{ color: 'whiteAlpha.600' }
);
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.200');
// API Key
const handleTranslate = async () => {
const apiKey = localStorage.getItem('apiKey');
setInputOnSubmit(inputMessage);
// Chat post conditions(maximum number of characters, valid message etc.)
const maxCodeLength = model === 'gpt-3.5-turbo' ? 700 : 700;
if (!apiKey?.includes('sk-')) {
alert('Please enter an API key.');
return;
}
if (!inputMessage) {
alert('Please enter your subject.');
return;
}
if (inputMessage.length > maxCodeLength) {
alert(
`Please enter code less than ${maxCodeLength} characters. You are currently at ${inputMessage.length} characters.`
);
return;
}
setOutputCode(' ');
setLoading(true);
const controller = new AbortController();
const body: ChatBody = {
inputMessage,
model,
apiKey
};
// -------------- Fetch --------------
const response = await fetch('/api/chatAPI', {
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;
while (!done) {
setLoading(true);
const { value, done: doneReading } = await reader.read();
done = doneReading;
const chunkValue = decoder.decode(value);
setOutputCode((prevCode) => prevCode + chunkValue);
}
setLoading(false);
};
// -------------- 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);
// };
const handleChange = (Event: any) => {
setInputMessage(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'}
pt={{ base: 5, md: 0 }}
position="relative"
>
<Image
width={{ base: '340px', xl: '350px' }}
src={Bg.src}
position="absolute"
left={{ base: '-20%', md: '35%', lg: '38%' }}
top={{ base: '50%' }}
zIndex="0"
w="200px"
transform="translate(0, -50%)"
alt=" "
/>
<Flex
mx="auto"
w="100%"
maxW="100%"
direction="column"
minH={{ base: '75vh', xl: '85vh' }}
>
{/* Model Change */}
<Flex w="100%" direction={'column'} mb={outputCode ? '20px' : 'auto'}>
<Flex
mx="auto"
mb="20px"
w="max-content"
borderRadius="60px"
zIndex={2}
>
<Flex
cursor={'pointer'}
justifyContent="center"
borderRadius="8px"
align={'center'}
py="16px"
transitionDuration={'0.3s'}
h="70px"
w="174px"
bg={model === 'gpt-3.5-turbo' ? opacityBg : 'transparent'}
boxShadow={model === 'gpt-3.5-turbo' ? shadow : 'unset'}
color={textColor}
fontWeight="700"
fontSize="18px"
onClick={() => setModel('gpt-3.5-turbo')}
>
<Flex
me="10px"
w="39px"
h="39px"
justify={'center'}
alignItems="center"
borderRadius="full"
bg={gradientBg}
>
<Icon
as={MdAutoAwesome}
color={brandColor}
h="20px"
w="20px"
/>
</Flex>
GPT-3.5
</Flex>
<Flex
cursor={'pointer'}
justifyContent="center"
borderRadius="8px"
align={'center'}
py="16px"
transitionDuration={'0.3s'}
h="70px"
w="174px"
bg={model === 'gpt-4-1106-preview' ? opacityBg : 'transparent'}
boxShadow={model === 'gpt-4-1106-preview' ? shadow : 'unset'}
color={textColor}
fontWeight="700"
fontSize="18px"
onClick={() => setModel('gpt-4-1106-preview')}
>
<Flex
me="10px"
w="39px"
h="39px"
justify={'center'}
alignItems="center"
borderRadius="full"
bg={gradientBg}
>
<Icon as={MdBolt} color={brandColor} h="20px" w="20px" />
</Flex>
GPT-4
</Flex>
</Flex>
<Accordion zIndex={10} mx="auto" my="0px" color={gray} allowToggle>
<AccordionItem border="none">
<AccordionButton
borderBottom="0px solid"
maxW="max-content"
mx="auto"
_hover={{ border: '0px solid', bg: 'none' }}
_focus={{ border: '0px solid', bg: 'none' }}
>
<Box textAlign={'center'}>
<Text
fontSize="sm"
fontWeight={'500'}
color="secondaryGray.500"
>
No plugins added
</Text>
</Box>
<AccordionIcon color="secondaryGray.500" />
</AccordionButton>
<AccordionPanel mx="auto" w="max-content" p="0px 0px 10px 0px">
<Text
fontSize="sm"
fontWeight={'500'}
color="secondaryGray.500"
textAlign={'center'}
>
This is a cool text example.
</Text>
</AccordionPanel>
</AccordionItem>
</Accordion>
</Flex>
{/* Main Box */}
<Flex
mx="auto"
w="100%"
direction={'column'}
display={outputCode ? 'flex' : 'none'}
mb="auto"
>
<Flex
mb="10px"
display={'flex'}
w="100%"
alignItems={'center'}
justifyContent="center"
>
<Flex
me="20px"
h="40px"
minH="40px"
minW="40px"
align="center"
justify={'center'}
borderRadius="full"
border="1px solid"
borderColor={borderColor}
>
<Icon as={MdPerson} color={brandColor} h="20px" w="20px" />
</Flex>
<Flex
zIndex={2}
borderRadius="14px"
w="100%"
border="1px solid"
borderColor={borderColor}
p="20px"
backdropFilter="blur(24px)"
>
<Text
color={textColor}
fontSize={{ base: 'sm', md: '16px' }}
fontWeight={'600'}
>
{inputOnSubmit}
</Text>
<Icon
as={MdEdit}
color={'gray.500'}
h="20px"
w="20px"
ms="auto"
cursor={'pointer'}
/>
</Flex>
</Flex>
<Flex w="100%">
<Flex
me="20px"
h="40px"
minW="40px"
align="center"
justify={'center'}
borderRadius="full"
background={gradientBg2}
>
<Icon as={MdAutoAwesome} color={'white'} h="20px" w="20px" />
</Flex>
<MessageBoxChat output={outputCode} />
</Flex>
</Flex>
{/* Chat Input */}
<Flex mt="20px" justifyContent={'flex-center'} mx="auto">
<Input
color={textColor}
border="1px solid"
borderRadius={'45px'}
borderColor={borderColor}
w={{ md: '100%', xl: '45vw' }}
h="60px"
id="email"
fontSize={'sm'}
fontWeight="500"
placeholder="Type your message here..."
_placeholder={placeholderColor}
_focus={{ borderColor: 'none' }}
mb={{ base: '14px', md: '16px' }}
me="20px"
onChange={handleChange}
/>
<Button
py="20px"
px="16px"
fontSize="sm"
variant="primary"
borderRadius="45px"
h="54px"
_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%)'
}
}}
minW="150px"
onClick={handleTranslate}
isLoading={loading}
>
Submit
</Button>
</Flex>
<Flex
mt="10px"
direction={{ base: 'column', md: 'row' }}
justifyContent="center"
alignItems={'center'}
>
<Text color={gray} textAlign="center" fontSize="xs">
Free Research Preview. ChatGPT may produce inaccurate information
about people, places, or facts. Consider checking important
information.
</Text>
</Flex>
</Flex>
</Flex>
</DashboardLayout>
);
}

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>
);
}

View File

@@ -0,0 +1,59 @@
'use client';
import Card from '@/components/card/Card';
import LineChart from '@/components/charts/LineChart';
import { lineChartDataMain } from '@/variables/charts';
import { lineChartOptionsMain } from '@/variables/charts';
import { Flex, useColorModeValue, Text, Box, Icon } from '@chakra-ui/react';
import { MdInsights } from 'react-icons/md';
function OverallRevenue() {
const newOptions = {
...lineChartOptionsMain,
// colors: ['var(--color-500)' ],
};
const bg = useColorModeValue('secondaryGray.300', 'secondaryGray.700');
const textColor = useColorModeValue('#120F43', 'white');
const brandColor = useColorModeValue('brand.500', 'white');
return (
<Card h="381px" p="24px">
<Flex align="center" gap="12px">
<Flex
h={'56px'}
w={'56px'}
justifyContent="center"
alignItems={'center'}
rounded="full"
fontSize="36px"
color={brandColor}
bg={bg}
>
<Icon as={MdInsights} w="24px" h="24px" />
</Flex>
<Box>
<Text as="h5" fontSize={'sm'} fontWeight="500" color="gray.700">
Credits usage in the last year
</Text>
<Text color={textColor} mt="4px" fontSize="24px" fontWeight={'700'}>
149,758
</Text>
</Box>
</Flex>
{/* Charts */}
<Flex
w="100%"
h="100%"
flex={{ sm: 'wrap', lg: 'nowrap' }}
overflow={{ '2xl': 'hidden' }}
>
<Box w="100%" h="100%">
<LineChart chartData={lineChartDataMain} chartOptions={newOptions} />
</Box>
</Flex>
</Card>
);
}
export default OverallRevenue;

View File

@@ -0,0 +1,341 @@
import Card from '@/components/card/Card';
import {
Box,
Button,
Checkbox,
Flex,
Table,
Tbody,
Td,
Text,
Th,
Thead,
Tr,
useColorModeValue,
} from '@chakra-ui/react';
import {
PaginationState,
createColumnHelper,
useReactTable,
ColumnFiltersState,
getCoreRowModel,
getFilteredRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFacetedMinMaxValues,
getPaginationRowModel,
getSortedRowModel,
flexRender,
} from '@tanstack/react-table';
import React from 'react';
import { MdChevronRight, MdChevronLeft } from 'react-icons/md';
type RowObj = {
checked?: string;
email: string;
provider: string;
created: string;
lastsigned: string;
uuid: string;
menu?: string;
};
function CheckTable(props: { tableData: any }) {
const { tableData } = props;
const textColor = useColorModeValue('#120F43', 'white');
const textColorSecondary = useColorModeValue('gray.700', 'white');
const borderColor = useColorModeValue('gray.200', 'gray.200');
const grayLight = useColorModeValue('gray.200', 'white');
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[],
);
let defaultData = tableData;
const [globalFilter, setGlobalFilter] = React.useState('');
// const createPages = (count: number) => {
// let arrPageCount = [];
// for (let i = 1; i <= count; i++) {
// arrPageCount.push(i);
// }
// return arrPageCount;
// };
const columns = [
columnHelper.accessor('checked', {
id: 'checked',
header: () => (
<Box w="max-content">
<Checkbox me="10px" />
</Box>
),
cell: (info: any) => (
<Flex w="max-content" alignItems="center">
<Checkbox
defaultChecked={info.getValue()[1]}
colorScheme="brand"
me="10px"
/>
</Flex>
),
}),
columnHelper.accessor('email', {
id: 'email',
header: () => (
<Text
justifyContent="space-between"
align="center"
fontSize={{ sm: '10px', lg: '12px' }}
color="gray.500"
>
EMAIL ADDRESS
</Text>
),
cell: (info) => (
<Text color={textColor} fontSize="sm" fontWeight="600">
{info.getValue()}
</Text>
),
}),
columnHelper.accessor('created', {
id: 'created',
header: () => (
<Text
justifyContent="space-between"
align="center"
fontSize={{ sm: '10px', lg: '12px' }}
color="gray.500"
>
CREATED
</Text>
),
cell: (info: any) => (
<Text color={textColor} fontSize="sm" fontWeight="600">
{info.getValue()}
</Text>
),
}),
columnHelper.accessor('provider', {
id: 'provider',
header: () => (
<Text
justifyContent="space-between"
align="center"
fontSize={{ sm: '10px', lg: '12px' }}
color="gray.500"
>
PROVIDER
</Text>
),
cell: (info: any) => (
<Text color={textColor} fontSize="sm" fontWeight="600">
{info.getValue()}
</Text>
),
}),
columnHelper.accessor('lastsigned', {
id: 'lastsigned',
header: () => (
<Text
justifyContent="space-between"
align="center"
fontSize={{ sm: '10px', lg: '12px' }}
color="gray.500"
>
LAST SIGN IN
</Text>
),
cell: (info) => (
<Text color={textColor} fontSize="sm" fontWeight="600">
{info.getValue()}
</Text>
),
}),
columnHelper.accessor('uuid', {
id: 'uuid',
header: () => (
<Text
justifyContent="space-between"
align="center"
fontSize={{ sm: '10px', lg: '12px' }}
color="gray.500"
>
USER UID
</Text>
),
cell: (info) => (
<Text color={textColor} fontSize="sm" fontWeight="600">
{info.getValue()}
</Text>
),
}),
]; // eslint-disable-next-line
const [data, setData] = React.useState(() => [...defaultData]);
const [{ pageIndex, pageSize }, setPagination] =
React.useState<PaginationState>({
pageIndex: 0,
pageSize: 11,
});
const pagination = React.useMemo(
() => ({
pageIndex,
pageSize,
}),
[pageIndex, pageSize],
);
const table = useReactTable({
data,
columns,
state: {
columnFilters,
globalFilter,
pagination,
},
onPaginationChange: setPagination,
onColumnFiltersChange: setColumnFilters,
onGlobalFilterChange: setGlobalFilter,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getFacetedRowModel: getFacetedRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),
getFacetedMinMaxValues: getFacetedMinMaxValues(),
debugTable: true,
debugHeaders: true,
debugColumns: false,
});
return (
<Card w="100%" h="100%" overflow={'auto'} p="0px">
<Box mt="20px" overflowX={{ base: 'scroll', xl: 'hidden' }}>
<Table w="100%">
<Thead>
{table.getHeaderGroups().map((headerGroup) => (
<Tr
key={headerGroup.id}
borderBottom="1px solid"
borderColor="gray.200"
p="20px"
>
{headerGroup.headers.map((header) => {
return (
<Th
key={header.id}
colSpan={header.colSpan}
onClick={header.column.getToggleSortingHandler()}
cursor="pointer"
borderBottom="1px solid"
borderColor={borderColor}
pb="14px"
ps="24px"
pe="16px"
textAlign={'start'}
>
<Flex
alignItems={'center'}
justifyContent="space-between"
fontSize="12px"
color={textColorSecondary}
// gray 1
>
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
{{
asc: '',
desc: '',
}[header.column.getIsSorted() as string] ?? null}
</Flex>
</Th>
);
})}
</Tr>
))}
</Thead>
<Tbody>
{table
.getRowModel()
.rows.slice(0, 7)
.map((row) => {
return (
<Tr key={row.id} px="24px">
{row.getVisibleCells().map((cell) => {
return (
<Td
key={cell.id}
w="max-content"
borderBottom="1px solid"
borderColor={borderColor}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</Td>
);
})}
</Tr>
);
})}
</Tbody>
</Table>
{/* pagination */}
<Flex
mt="8px"
w="100%"
h="80px"
alignItems={'center'}
justifyContent="space-between"
px="24px"
>
{/* left side */}
<Flex alignItems={'center'} gap="12px">
<Text color={textColorSecondary} fontWeight="500" fontSize="sm">
Showing 6 rows per page
</Text>
</Flex>
{/* right side */}
<Flex alignItems="center" gap="8px">
<Button
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
variant="transparent"
display={'flex'}
alignItems="center"
justifyContent="center"
borderRadius="full"
bg="transparent"
p="8px"
fontSize={'18px'}
color="gray.700"
>
<MdChevronLeft />
</Button>
<Button
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
variant="transparent"
display={'flex'}
alignItems="center"
justifyContent="center"
borderRadius="full"
bg="transparent"
p="8px"
fontSize={'18px'}
color="gray.700"
>
<MdChevronRight />
</Button>
</Flex>
</Flex>
</Box>
</Card>
);
}
export default CheckTable;
const columnHelper = createColumnHelper<RowObj>();

View File

@@ -0,0 +1,49 @@
import Card from '@/components/card/Card';
import { Box, Flex, Text, useColorModeValue } from '@chakra-ui/react';
const Statistics = (props: {
icon?: JSX.Element;
title: string;
value: number | string;
endContent?: JSX.Element;
}) => {
const { icon, title, value, endContent } = props;
const textColorSecondary = useColorModeValue('gray.700', 'white');
const textColor = useColorModeValue('#120F43', 'white');
return (
<Card
w="100%"
justifyContent="space-between"
borderRadius="14px"
bg="white"
py="30px"
>
<Flex gap="12px" alignItems={'center'}>
{icon}
<Box>
<Text
as="h5"
fontSize="sm"
fontWeight={'500'}
color={textColorSecondary}
>
{title}
</Text>
<Text
color={textColor}
fontWeight="700"
mt="4px"
fontSize="24px"
lineHeight={'24px'}
>
{value}
</Text>
</Box>
</Flex>
{endContent}
</Card>
);
};
export default Statistics;

View File

@@ -0,0 +1,144 @@
/*eslint-disable*/
'use client';
import MainChart from '@/components/dashboard/main/cards/MainChart';
import MainDashboardTable from '@/components/dashboard/main/cards/MainDashboardTable';
import Statistics from '@/components/dashboard/main/cards/Statistics';
import DashboardLayout from '@/components/layout';
import { Database } from '@/types_db';
import tableDataUserReports from '@/variables/tableDataUserReports';
import { Box, Flex, Grid, Icon, useColorModeValue } from '@chakra-ui/react';
import { User } from '@supabase/supabase-js';
import { HiOutlineChip } from 'react-icons/hi';
import { MdOutlineGroup, MdOutlineGroupAdd, MdKey } from 'react-icons/md';
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 | any;
userDetails: { [x: string]: any } | null | any;
}
export default function Main(props: Props) {
const bg = useColorModeValue('secondaryGray.300', 'whiteAlpha.200');
const brandColor = useColorModeValue('brand.500', 'white');
console.log(props.user);
return (
<DashboardLayout
userDetails={props.userDetails}
user={props?.user}
products={props.products}
subscription={props.subscription}
title="Main Dashboard"
description="Manage your dashboard"
>
<Box h="100%" w="100%">
<Grid
mb="20px"
w="100%"
gridTemplateColumns={{
base: 'repeat(1, minmax(0, 1fr))',
md: 'repeat(2, minmax(0, 1fr))',
xl: 'repeat(4, minmax(0, 1fr))'
}}
borderRadius="14px"
gap="20px"
>
{/* statistics */}
<Statistics
icon={
<Flex
h={'56px'}
w={'56px'}
justifyContent="center"
alignItems={'center'}
rounded="full"
fontSize="36px"
color={brandColor}
bg={bg}
>
<Icon as={MdOutlineGroup} w="24px" h="24px" />
</Flex>
}
title="Credits used in the last month"
value="46,042"
/>
<Statistics
icon={
<Flex
h={'56px'}
w={'56px'}
justifyContent="center"
alignItems={'center'}
rounded="full"
fontSize="36px"
color={brandColor}
bg={bg}
>
<Icon as={MdOutlineGroupAdd} w="24px" h="24px" />
</Flex>
}
title="Total Credits"
value="149,758"
/>
<Statistics
icon={
<Flex
h={'56px'}
w={'56px'}
justifyContent="center"
alignItems={'center'}
rounded="full"
fontSize="36px"
color={brandColor}
bg={bg}
>
<Icon as={HiOutlineChip} w="24px" h="24px" />
</Flex>
}
title="Plan Credits"
value="100,000"
/>
<Statistics
icon={
<Flex
h={'56px'}
w={'56px'}
justifyContent="center"
alignItems={'center'}
rounded="full"
fontSize="36px"
color={brandColor}
bg={bg}
>
<Icon as={MdKey} w="24px" h="24px" />
</Flex>
}
title="Current Plan"
value="Expert+"
/>
</Grid>
<Box mb="20px">
<MainChart />
</Box>
{/* Conversion and talbes*/}
<Box w="full" h="full" borderRadius="14px">
<MainDashboardTable tableData={tableDataUserReports} />
</Box>
</Box>
</DashboardLayout>
);
}

View File

@@ -0,0 +1,14 @@
import { getUser } from '@/utils/supabase/queries';
import { redirect } from 'next/navigation';
import { createClient } from '@/utils/supabase/server';
export default async function Dashboard() {
const supabase = createClient();
const [user] = await Promise.all([getUser(supabase)]);
if (!user) {
return redirect('/dashboard/signin');
} else {
redirect('/dashboard/main');
}
}

View File

@@ -0,0 +1,534 @@
/*eslint-disable*/
'use client';
import MessageBox from '@/components/MessageBox';
import Card from '@/components/card/Card';
import DashboardLayout from '@/components/layout';
import { OpenAIModel, PremiumEssayBody } from '@/types/types';
import { Database } from '@/types_db';
import {
Button,
Flex,
FormLabel,
Select,
Switch,
Text,
Textarea,
useColorModeValue,
useToast
} from '@chakra-ui/react';
import { User } from '@supabase/supabase-js';
import { useState } from 'react';
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 PremiumEssayGenerator(props: Props) {
// Input States
const [words, setWords] = useState<string>('200-300');
const [essayType, setEssayType] = useState<
| ''
| 'Argumentative'
| 'Classic'
| 'Persuasive'
| 'Memoir'
| 'Critique'
| 'Compare/Contrast'
| 'Narrative'
| 'Descriptive'
| 'Expository'
| 'Cause and Effect'
| 'Reflective'
| 'Informative'
>('');
const [topic, setTopic] = useState<string>('');
const [tone, setTone] = useState<string>('');
const [citation, setCitation] = useState<string>('');
const [citations, setCitations] = useState(false);
const [level, setLevel] = 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<any>();
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 (!topic) {
alert('Please enter your subject.');
return;
}
if (!essayType) {
alert('Please choose a type of essay');
return;
}
if (!tone) {
alert('Please choose a essay tone');
return;
}
if (!citation) {
alert('Please choose a citation format');
return;
}
if (!level) {
alert('Please choose an level');
return;
}
if (topic.length > maxCodeLength) {
alert(
`Please enter a topic less than ${maxCodeLength} characters. You are currently at ${topic.length} characters.`
);
return;
}
setLoading(true);
setOutputCode('');
const controller = new AbortController();
const body: PremiumEssayBody = {
words,
topic,
essayType,
tone,
citation,
level,
citations,
model
};
// -------------- Fetch --------------
const response = await fetch('/api/premiumEssayAPI', {
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 handleChangeWords = (Event: any) => {
setWords(Event.target.value);
};
const handleChange = (Event: any) => {
setTopic(Event.target.value);
};
const handleChangeEssayType = (Event: any) => {
setEssayType(Event.target.value);
};
const handleChangeEssayTone = (Event: any) => {
setTone(Event.target.value);
};
const handleChangeCitation = (Event: any) => {
setCitation(Event.target.value);
};
const handleChangeLevel = (Event: any) => {
setLevel(Event.target.value);
};
const handleCitations = (Event: any) => {
setCitations(!citations);
};
return (
<>
<DashboardLayout
userDetails={props.userDetails}
user={props?.user}
products={props.products}
subscription={props.subscription}
title="Premium Generator"
description="Premium 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 premium essay will be about?
</Text>
<Textarea
border="1px solid"
borderRadius={'10px'}
borderColor={borderColor}
p="15px 20px"
mb="28px"
minH="124px"
fontWeight="500"
_focus={{ borderColor: 'none' }}
color={textColor}
placeholder="Type here your topic..."
_placeholder={placeholderColor}
onChange={handleChange}
/>
<FormLabel
display="flex"
ms="10px"
htmlFor={'words'}
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="words"
placeholder="Select option"
_focus={{ borderColor: 'none' }}
mb="28px"
onChange={handleChangeWords}
>
<option value="200-300">200-300</option>
<option value="300-400">300-400</option>
<option value="400-500">400-500</option>
<option value="500-600">500-600</option>
<option value="700-900">700-900</option>
<option value="1000-1500">1000-1500</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="Compare/Contrast">Compare/Contrast</option>
<option value="Persuasive">Persuasive</option>
<option value="Critique">Critique</option>
<option value="Memoir">Memoir</option>
<option value="Narrative">Narrative</option>
<option value="Descriptive">Descriptive</option>
<option value="Expository">Expository</option>
<option value="Cause and Effect">Cause and Effect</option>
<option value="Reflective">Reflective</option>
<option value="Informative">Informative</option>
</Select>
<FormLabel
display="flex"
ms="10px"
htmlFor={'tone'}
fontSize="md"
color={textColor}
letterSpacing="0px"
fontWeight="bold"
_hover={{ cursor: 'pointer' }}
>
Select your Essay Tone
</FormLabel>
<Select
border="1px solid"
borderRadius={'10px'}
borderColor={borderColor}
h="60px"
id="tone"
placeholder="Select option"
_focus={{ borderColor: 'none' }}
mb="28px"
onChange={handleChangeEssayTone}
>
<option value="Academic">Academic</option>
<option value="Sarcastic">Sarcastic</option>
<option value="Informal">Informal</option>
<option value="Assertive">Assertive</option>
<option value="Friendly">Friendly</option>
<option value="Humorous">Humorous</option>
<option value="Formal">Formal</option>
</Select>
<FormLabel
display="flex"
ms="10px"
htmlFor={'citation'}
fontSize="md"
color={textColor}
letterSpacing="0px"
fontWeight="bold"
_hover={{ cursor: 'pointer' }}
>
Select your Citation Format
</FormLabel>
<Select
border="1px solid"
borderRadius={'10px'}
borderColor={borderColor}
h="60px"
id="citation"
placeholder="Select option"
_focus={{ borderColor: 'none' }}
mb="28px"
onChange={handleChangeCitation}
>
<option value="Cambridge Style">Cambridge Style</option>
<option value="Harvard Style">Harvard Style</option>
<option value="MLA">MLA</option>
<option value="Chicago Style">Chicago Style</option>
<option value="APA">APA</option>
<option value="AMA">AMA</option>
<option value="Oxford Style">Oxford Style</option>
<option value="IEEE">IEEE</option>
<option value="CSE">CSE</option>
<option value="Bluebook">Bluebook</option>
<option value="Turabian">Turabian</option>
<option value="Vancouver">Vancouver</option>
<option value="ACS">ACS</option>
<option value="NLM">NLM</option>
<option value="AAA">AAA</option>
<option value="ASA">ASA</option>
<option value="APSA">APSA</option>
</Select>
<FormLabel
display="flex"
ms="10px"
htmlFor={'level'}
fontSize="md"
color={textColor}
letterSpacing="0px"
fontWeight="bold"
_hover={{ cursor: 'pointer' }}
>
Academic Level
</FormLabel>
<Select
border="1px solid"
borderRadius={'10px'}
borderColor={borderColor}
h="60px"
id="level"
placeholder="Select option"
_focus={{ borderColor: 'none' }}
mb="28px"
onChange={handleChangeLevel}
>
<option value="High-School">High-School</option>
<option value="Pre Final">Pre Final</option>
<option value="Master">Master</option>
<option value="Doctoral">Doctoral</option>
<option value="Final Year">Final Year</option>
</Select>
<Flex
mb="28px"
align={{ base: 'unset', md: 'center' }}
direction={{ base: 'column', md: 'row' }}
>
<FormLabel
display="flex"
ms="10px"
me={{ base: '0px', md: '20px' }}
mb={{ base: '10px', md: '0px' }}
htmlFor={'citations'}
fontSize="md"
color={textColor}
letterSpacing="0px"
fontWeight="bold"
_hover={{ cursor: 'pointer' }}
>
Citations and refrences
</FormLabel>
<Switch
isChecked={citations}
onChange={handleCitations}
colorScheme="brandScheme"
id="citations"
/>
</Flex>
<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>
</>
);
}

View File

@@ -0,0 +1,308 @@
/*eslint-disable*/
'use client';
// import ManageSubscriptionButton from './ManageSubscriptionButton';
import Card from '@/components/card/Card';
import DashboardLayout from '@/components/layout';
import { HSeparator } from '@/components/separator/Separator';
import { Database } from '@/types_db';
import { handleRequest } from '@/utils/auth-helpers/client';
import { updateEmail, updateName } from '@/utils/auth-helpers/server';
import {
Button,
Flex,
FormLabel,
Input,
Text,
useColorModeValue
} from '@chakra-ui/react';
import { User } from '@supabase/supabase-js';
import { redirect, useRouter } from 'next/navigation';
import { useState } from 'react';
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 Settings(props: Props) {
const [isSubmitting, setIsSubmitting] = useState(false);
const router = useRouter();
const textColor = useColorModeValue('#120F43', 'white');
const placeholderColor = useColorModeValue(
{ color: 'gray.500' },
{ color: 'whiteAlpha.600' }
);
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.200');
// -------------- Input Value Handler --------------
const handleSubmitEmail = async (e: React.FormEvent<HTMLFormElement>) => {
setIsSubmitting(true);
// Check if the new email is the same as the old email
if (e.currentTarget.newEmail.value === props.user.email) {
e.preventDefault();
setIsSubmitting(false);
return;
}
handleRequest(e, updateEmail, router);
setIsSubmitting(false);
};
const handleSubmitName = async (e: React.FormEvent<HTMLFormElement>) => {
setIsSubmitting(true);
// Check if the new name is the same as the old name
if (e.currentTarget.fullName.value === props.user.user_metadata.full_name) {
e.preventDefault();
setIsSubmitting(false);
return;
}
handleRequest(e, updateName, router);
setIsSubmitting(false);
};
if (!props.user) {
return redirect('/dashboard/signin');
}
return (
<>
<DashboardLayout
userDetails={props.userDetails}
user={props?.user}
products={props.products}
subscription={props.subscription}
title="Account Settings"
description="Profile settings."
>
<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%' }}
maxW={{ base: '100%' }}
px={{ base: '10px', md: '20px', lg: '20px' }}
py={{ base: '28px', md: '20px', lg: '30px' }}
w="820px"
h="min-content"
me={{ base: '0px', md: '20px' }}
mb={{ base: '20px', md: '0px' }}
>
<Text
ps={{ base: '10px', md: '32px' }}
fontSize={{ base: 'lg', md: '30px' }}
color={textColor}
fontWeight="800"
>
Account Settings
</Text>
<Text
ps={{ base: '10px', md: '32px' }}
fontSize={{ base: 'sm', md: 'md' }}
color="gray.500"
fontWeight="500"
mb="30px"
>
Here you can change your account information
</Text>
<FormLabel
px={{ base: '10px', md: '32px' }}
display="flex"
ms="10px"
htmlFor={'fullName'}
fontSize="md"
color={textColor}
letterSpacing="0px"
fontWeight="bold"
_hover={{ cursor: 'pointer' }}
lineHeight="100%"
mb="12px"
>
Your Name
<Text
fontSize={'14px'}
color="gray.500"
fontWeight="500"
ms="4px"
>
{' '}
(30 characters maximum)
</Text>
</FormLabel>
<Flex
direction={{ base: 'column', md: 'row' }}
px={{ base: '10px', md: '32px' }}
mb={{ base: '30px', md: '0px' }}
>
<form
style={{ width: '100%' }}
id="nameForm"
onSubmit={(e) => handleSubmitName(e)}
>
<Input
color={textColor}
border="1px solid"
borderRadius={'45px'}
borderColor={borderColor}
h="60px"
type="text"
name="fullName"
id="fullName"
defaultValue={props.user?.user_metadata.full_name ?? ''}
fontWeight="500"
placeholder="Please enter your full name"
_placeholder={placeholderColor}
_focus={{ borderColor: 'none' }}
mb={{ base: '14px', md: '26px' }}
me="20px"
/>
</form>
<Button
py="20px"
px="16px"
fontSize="sm"
ms="10px"
variant="primary"
borderRadius="45px"
h="54px"
form="nameForm"
type="submit"
_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%)'
}
}}
minW="150px"
>
Update name
</Button>
<HSeparator
bg="gray.200"
mt={{ base: '30px', md: '0px' }}
display={{ md: 'none' }}
alignSelf="center"
maxW="90%"
/>
</Flex>
{/* <Text
px={{ base: '10px', md: '36px' }}
color="red"
mb="20px"
display={nameError?.status ? 'block' : 'none'}
>
{nameError?.message}
</Text> */}
<FormLabel
px={{ base: '10px', md: '32px' }}
display="flex"
htmlFor={'email'}
fontSize="md"
flexDirection={{ base: 'column', md: 'row' }}
color={textColor}
letterSpacing="0px"
fontWeight="bold"
_hover={{ cursor: 'pointer' }}
lineHeight="100%"
mb="12px"
>
Your Email
<Text
fontSize={'14px'}
color="gray.500"
fontWeight="500"
ms={{ base: '0px', md: '4px' }}
mt={{ base: '6px', md: '0px' }}
>
{' '}
(We will email you to verify the change)
</Text>
</FormLabel>
<Flex
direction={{ base: 'column', md: 'row' }}
px={{ base: '10px', md: '32px' }}
>
<form
style={{ width: '100%' }}
id="emailForm"
onSubmit={(e) => handleSubmitEmail(e)}
>
<Input
type="text"
name="newEmail"
id="email"
color={textColor}
border="1px solid"
borderRadius={'45px'}
borderColor={borderColor}
h="60px"
fontWeight="500"
placeholder="Please enter your email"
_placeholder={placeholderColor}
_focus={{ borderColor: 'none' }}
defaultValue={props.user.email ?? ''}
mb={{ base: '14px', md: '26px' }}
me="20px"
/>
</form>
<Button
py="20px"
px="16px"
fontSize="sm"
type="submit"
form="emailForm"
variant="primary"
borderRadius="45px"
ms="10px"
h="54px"
_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%)'
}
}}
minW="150px"
>
Update email
</Button>
</Flex>
</Card>
</Flex>
</Flex>
</DashboardLayout>
</>
);
}

View File

@@ -0,0 +1,263 @@
/*eslint-disable*/
'use client';
import Card from '@/components/card/Card';
import DashboardLayout from '@/components/layout';
import { Database } from '@/types_db';
import { createStripePortal } from '@/utils/stripe/server';
import { Badge, Button, Flex, Icon, Link, Text } from '@chakra-ui/react';
import { User } from '@supabase/supabase-js';
import { redirect, usePathname, useRouter } from 'next/navigation';
import { useState } from 'react';
import { MdChevronRight } from 'react-icons/md';
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 | any;
userDetails: { [x: string]: any } | null | any;
}
export default function Subscription(props: Props) {
const router = useRouter();
const [isSubmitting, setIsSubmitting] = useState(false);
const currentPath = usePathname();
const handleStripePortalRequest = async () => {
setIsSubmitting(true);
const redirectUrl = await createStripePortal(currentPath);
setIsSubmitting(false);
return router.push(redirectUrl);
};
if (!props.user) {
return redirect('/dashboard/signin');
}
return (
<>
<DashboardLayout
userDetails={props.userDetails}
user={props?.user}
products={props.products}
subscription={props.subscription}
title="Subscription Page"
description="Manage your subscriptions"
>
<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 w="830px" maxW={{ base: '100%' }} h="min-content">
<Card
bg="linear-gradient(15deg, #4A25E1 26.3%, #7B5AFF 86.4%)"
maxW={{ base: '100%' }}
px={{ base: '20px', md: '40px', lg: '50px' }}
py={{ base: '28px', md: '40px', lg: '50px' }}
me={{ base: '0px', md: '20px' }}
mb="16px"
>
<Badge
w="max-content"
mb="10px"
fontSize="sm"
bg="rgba(255,255,255,0.12)"
color="white"
fontWeight="bold"
borderRadius="8px"
>
CURRENT PLAN
</Badge>
{props.subscription ? (
props.products?.map((product: any) => {
const price = product?.prices?.find(
(price: any) => price.id === props.subscription?.price_id
);
// {props.subscription?.map((subscription:any) => {
// const price = subscription?.prices?.find(
// (user:any) => user.id === props?.userDetails.id,
// );
if (price?.id === props.subscription.price_id)
return (
// IN CASE USER HAS PLAN
<Flex
justifyContent="space-between"
direction={{ base: 'column', md: 'row' }}
>
<Flex direction="column">
<Text
fontSize={{ base: '30px', md: '44px' }}
color="white"
fontWeight="800"
>
{product?.name
? product.name?.toString()
: 'No plan active'}
</Text>
<Text
fontSize={{ base: 'sm', md: '20px' }}
color="white"
fontWeight="500"
mb={{ base: '20px', md: '0px' }}
>
{product?.name
? `You are currently on ${product.name?.toString()}`
: "You don't have an active subscription."}
</Text>
</Flex>
<Flex direction="column">
<Text
fontSize={{ base: 'lg', md: '24px' }}
color="white"
textAlign={{ base: 'left', md: 'right' }}
fontWeight="800"
mb="10px"
>
$
{price?.unit_amount !== null
? price?.unit_amount / 100
: '0'}
{price?.interval === 'year'
? '/year'
: price?.interval === 'month'
? '/month'
: 'error'}
</Text>
<Button
py="20px"
px="16px"
fontSize="sm"
variant="outline"
borderRadius="45px"
w={{ base: '100%', md: '266px' }}
h="54px"
onClick={handleStripePortalRequest}
>
{props?.subscription
? 'Manage your subscription'
: 'See pricing Plans'}
<Icon
as={MdChevronRight}
display={props?.subscription ? 'none' : 'unset'}
mt="2px"
h="16px"
w="16px"
/>
</Button>
</Flex>
</Flex>
);
})
) : (
// IN CASE OF NOW PLAN
<Flex
justifyContent="space-between"
direction={{ base: 'column', md: 'row' }}
>
<Flex direction="column">
<Text
fontSize={{ base: '30px', md: '44px' }}
color="white"
fontWeight="800"
>
No plan active
</Text>
<Text
fontSize={{ base: 'sm', md: '20px' }}
color="white"
fontWeight="500"
mb={{ base: '20px', md: '0px' }}
>
You don't have an active subscription.
</Text>
</Flex>
<Flex direction="column">
<Text
fontSize={{ base: 'lg', md: '24px' }}
color="white"
textAlign={{ base: 'left', md: 'right' }}
fontWeight="800"
mb="10px"
>
$0/month
</Text>
<Link href="/pricing">
<Button
py="20px"
px="16px"
fontSize="sm"
variant="outline"
borderRadius="45px"
w={{ base: '100%', md: '190px' }}
h="54px"
>
See pricing Plans
<Icon
as={MdChevronRight}
mt="2px"
h="16px"
w="16px"
/>
</Button>
</Link>
</Flex>
</Flex>
)}
</Card>
<Flex
direction={{ base: 'column', md: 'row' }}
justifyContent="center"
>
<Text
textAlign={'center'}
fontWeight={'500'}
fontSize="sm"
alignSelf="center"
color="gray.500"
w={{ base: '70%', md: 'unset' }}
me="2px"
>
Got a question regarding your subscription? Chat with us via{' '}
</Text>
<Link href="mailto:hello@horizon-ui.com">
<Text
textAlign={'center'}
fontWeight={'500'}
fontSize="sm"
textDecoration="underline"
color="gray.500"
>
hello@horizon-ui.com.
</Text>
</Link>
</Flex>
</Card>
</Flex>
</Flex>
</DashboardLayout>
</>
);
}

View File

@@ -0,0 +1,49 @@
import Card from '@/components/card/Card';
import { Box, Flex, Text, useColorModeValue } from '@chakra-ui/react';
const Statistics = (props: {
icon?: JSX.Element;
title: string;
value: number | string;
endContent?: JSX.Element;
}) => {
const { icon, title, value, endContent } = props;
const textColorSecondary = useColorModeValue('gray.700', 'white');
const textColor = useColorModeValue('#120F43', 'white');
return (
<Card
w="100%"
justifyContent="space-between"
borderRadius="14px"
bg="white"
py="30px"
>
<Flex gap="12px" alignItems={'center'}>
{icon}
<Box>
<Text
as="h5"
fontSize="sm"
fontWeight={'500'}
color={textColorSecondary}
>
{title}
</Text>
<Text
color={textColor}
fontWeight="700"
mt="4px"
fontSize="24px"
lineHeight={'24px'}
>
{value}
</Text>
</Box>
</Flex>
{endContent}
</Card>
);
};
export default Statistics;

View File

@@ -0,0 +1,341 @@
import Card from '@/components/card/Card';
import {
Box,
Button,
Checkbox,
Flex,
Table,
Tbody,
Td,
Text,
Th,
Thead,
Tr,
useColorModeValue,
} from '@chakra-ui/react';
import {
PaginationState,
createColumnHelper,
useReactTable,
ColumnFiltersState,
getCoreRowModel,
getFilteredRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
getFacetedMinMaxValues,
getPaginationRowModel,
getSortedRowModel,
flexRender,
} from '@tanstack/react-table';
import React from 'react';
import { MdChevronRight, MdChevronLeft } from 'react-icons/md';
type RowObj = {
checked?: string;
email: string;
provider: string;
created: string;
lastsigned: string;
uuid: string;
menu?: string;
};
function CheckTable(props: { tableData: any }) {
const { tableData } = props;
const textColor = useColorModeValue('#120F43', 'white');
const textColorSecondary = useColorModeValue('gray.700', 'white');
const borderColor = useColorModeValue('gray.200', 'gray.200');
const grayLight = useColorModeValue('gray.200', 'white');
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[],
);
let defaultData = tableData;
const [globalFilter, setGlobalFilter] = React.useState('');
// const createPages = (count: number) => {
// let arrPageCount = [];
// for (let i = 1; i <= count; i++) {
// arrPageCount.push(i);
// }
// return arrPageCount;
// };
const columns = [
columnHelper.accessor('checked', {
id: 'checked',
header: () => (
<Box w="max-content">
<Checkbox me="10px" />
</Box>
),
cell: (info: any) => (
<Flex w="max-content" alignItems="center">
<Checkbox
defaultChecked={info.getValue()[1]}
colorScheme="brand"
me="10px"
/>
</Flex>
),
}),
columnHelper.accessor('email', {
id: 'email',
header: () => (
<Text
justifyContent="space-between"
align="center"
fontSize={{ sm: '10px', lg: '12px' }}
color="gray.500"
>
EMAIL ADDRESS
</Text>
),
cell: (info) => (
<Text color={textColor} fontSize="sm" fontWeight="600">
{info.getValue()}
</Text>
),
}),
columnHelper.accessor('created', {
id: 'created',
header: () => (
<Text
justifyContent="space-between"
align="center"
fontSize={{ sm: '10px', lg: '12px' }}
color="gray.500"
>
CREATED
</Text>
),
cell: (info: any) => (
<Text color={textColor} fontSize="sm" fontWeight="600">
{info.getValue()}
</Text>
),
}),
columnHelper.accessor('provider', {
id: 'provider',
header: () => (
<Text
justifyContent="space-between"
align="center"
fontSize={{ sm: '10px', lg: '12px' }}
color="gray.500"
>
PROVIDER
</Text>
),
cell: (info: any) => (
<Text color={textColor} fontSize="sm" fontWeight="600">
{info.getValue()}
</Text>
),
}),
columnHelper.accessor('lastsigned', {
id: 'lastsigned',
header: () => (
<Text
justifyContent="space-between"
align="center"
fontSize={{ sm: '10px', lg: '12px' }}
color="gray.500"
>
LAST SIGN IN
</Text>
),
cell: (info) => (
<Text color={textColor} fontSize="sm" fontWeight="600">
{info.getValue()}
</Text>
),
}),
columnHelper.accessor('uuid', {
id: 'uuid',
header: () => (
<Text
justifyContent="space-between"
align="center"
fontSize={{ sm: '10px', lg: '12px' }}
color="gray.500"
>
USER UID
</Text>
),
cell: (info) => (
<Text color={textColor} fontSize="sm" fontWeight="600">
{info.getValue()}
</Text>
),
}),
]; // eslint-disable-next-line
const [data, setData] = React.useState(() => [...defaultData]);
const [{ pageIndex, pageSize }, setPagination] =
React.useState<PaginationState>({
pageIndex: 0,
pageSize: 11,
});
const pagination = React.useMemo(
() => ({
pageIndex,
pageSize,
}),
[pageIndex, pageSize],
);
const table = useReactTable({
data,
columns,
state: {
columnFilters,
globalFilter,
pagination,
},
onPaginationChange: setPagination,
onColumnFiltersChange: setColumnFilters,
onGlobalFilterChange: setGlobalFilter,
getCoreRowModel: getCoreRowModel(),
getFilteredRowModel: getFilteredRowModel(),
getSortedRowModel: getSortedRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getFacetedRowModel: getFacetedRowModel(),
getFacetedUniqueValues: getFacetedUniqueValues(),
getFacetedMinMaxValues: getFacetedMinMaxValues(),
debugTable: true,
debugHeaders: true,
debugColumns: false,
});
return (
<Card w="100%" h="100%" overflow={'auto'} p="0px">
<Box mt="20px" overflowX={{ base: 'scroll', xl: 'hidden' }}>
<Table w="100%">
<Thead>
{table.getHeaderGroups().map((headerGroup) => (
<Tr
key={headerGroup.id}
borderBottom="1px solid"
borderColor="gray.200"
p="20px"
>
{headerGroup.headers.map((header) => {
return (
<Th
key={header.id}
colSpan={header.colSpan}
onClick={header.column.getToggleSortingHandler()}
cursor="pointer"
borderBottom="1px solid"
borderColor={borderColor}
pb="14px"
ps="24px"
pe="16px"
textAlign={'start'}
>
<Flex
alignItems={'center'}
justifyContent="space-between"
fontSize="12px"
color={textColorSecondary}
// gray 1
>
{flexRender(
header.column.columnDef.header,
header.getContext(),
)}
{{
asc: '',
desc: '',
}[header.column.getIsSorted() as string] ?? null}
</Flex>
</Th>
);
})}
</Tr>
))}
</Thead>
<Tbody>
{table
.getRowModel()
.rows.slice(0, 7)
.map((row) => {
return (
<Tr key={row.id} px="24px">
{row.getVisibleCells().map((cell) => {
return (
<Td
key={cell.id}
w="max-content"
borderBottom="1px solid"
borderColor={borderColor}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</Td>
);
})}
</Tr>
);
})}
</Tbody>
</Table>
{/* pagination */}
<Flex
mt="8px"
w="100%"
h="80px"
alignItems={'center'}
justifyContent="space-between"
px="24px"
>
{/* left side */}
<Flex alignItems={'center'} gap="12px">
<Text color={textColorSecondary} fontWeight="500" fontSize="sm">
Showing 6 rows per page
</Text>
</Flex>
{/* right side */}
<Flex alignItems="center" gap="8px">
<Button
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
variant="transparent"
display={'flex'}
alignItems="center"
justifyContent="center"
borderRadius="full"
bg="transparent"
p="8px"
fontSize={'18px'}
color="gray.700"
>
<MdChevronLeft />
</Button>
<Button
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
variant="transparent"
display={'flex'}
alignItems="center"
justifyContent="center"
borderRadius="full"
bg="transparent"
p="8px"
fontSize={'18px'}
color="gray.700"
>
<MdChevronRight />
</Button>
</Flex>
</Flex>
</Box>
</Card>
);
}
export default CheckTable;
const columnHelper = createColumnHelper<RowObj>();

View File

@@ -0,0 +1,144 @@
/*eslint-disable*/
'use client';
import Statistics from '@/components/dashboard/users-list/cards/Statistics';
import UserListTable from '@/components/dashboard/users-list/cards/UserListTable';
import DashboardLayout from '@/components/layout';
import { Database } from '@/types_db';
import tableDataUserReports from '@/variables/tableDataUserReports';
import { Box, Flex, Grid, Icon, useColorModeValue } from '@chakra-ui/react';
import { User } from '@supabase/supabase-js';
import { redirect } from 'next/navigation';
import { MdOutlineGroup, MdOutlineGroupAdd, MdKey } from 'react-icons/md';
import { TbDatabase } from 'react-icons/tb';
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 | any;
userDetails: { [x: string]: any } | null | any;
}
export default function Settings(props: Props) {
if (!props.user) {
return redirect('/dashboard/signin');
}
const bg = useColorModeValue('secondaryGray.300', 'whiteAlpha.200');
const brandColor = useColorModeValue('brand.500', 'white');
return (
<DashboardLayout
userDetails={props.userDetails}
user={props?.user}
products={props.products}
subscription={props.subscription}
title="Subscription Page"
description="Manage your subscriptions"
>
<Box mt="12px" h="100%" w="100%">
<Grid
mb="20px"
gap="20px"
gridTemplateColumns={{
base: 'repeat(1, minmax(0, 1fr))',
md: 'repeat(2, minmax(0, 1fr))',
xl: 'repeat(4, minmax(0, 1fr))'
}}
borderRadius="14px"
>
{/* statistics */}
<Statistics
icon={
<Flex
h={'56px'}
w={'56px'}
justifyContent="center"
alignItems={'center'}
rounded="full"
fontSize="36px"
color={brandColor}
bg={bg}
>
<Icon as={MdOutlineGroup} w="24px" h="24px" />
</Flex>
}
title="Total Users"
value="9,794"
/>
<Statistics
icon={
<Flex
h={'56px'}
w={'56px'}
justifyContent="center"
alignItems={'center'}
rounded="full"
fontSize="36px"
color={brandColor}
bg={bg}
>
<Icon as={MdOutlineGroupAdd} w="24px" h="24px" />
</Flex>
}
title="Users Today"
value="379"
/>
<Statistics
icon={
<Flex
h={'56px'}
w={'56px'}
justifyContent="center"
alignItems={'center'}
rounded="full"
fontSize="36px"
color={brandColor}
bg={bg}
>
<Icon as={TbDatabase} w="24px" h="24px" />
</Flex>
}
title="REST Requests"
value="270,307"
/>
<Statistics
icon={
<Flex
h={'56px'}
w={'56px'}
justifyContent="center"
alignItems={'center'}
rounded="full"
fontSize="36px"
color={brandColor}
bg={bg}
>
<Icon as={MdKey} w="24px" h="24px" />
</Flex>
}
title="Auth Requests"
value="23,484"
/>
</Grid>
{/* Conversion and talbes*/}
<Flex h="100%" w="100%" borderRadius="14px">
<UserListTable tableData={tableDataUserReports} />
</Flex>
</Box>
</DashboardLayout>
);
}