update pay function
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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>();
|
||||
@@ -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;
|
||||
144
boilerplate-chakra-pro-main/components/dashboard/main/index.tsx
Normal file
144
boilerplate-chakra-pro-main/components/dashboard/main/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
14
boilerplate-chakra-pro-main/components/dashboard/page.tsx
Normal file
14
boilerplate-chakra-pro-main/components/dashboard/page.tsx
Normal 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');
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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>();
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user