update pay function
This commit is contained in:
26
boilerplate-chakra-pro-main/.env.local.example
Normal file
26
boilerplate-chakra-pro-main/.env.local.example
Normal file
@@ -0,0 +1,26 @@
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://********************.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=****************************************************************************************************************************************************************************************************************
|
||||
|
||||
NEXT_PUBLIC_OPENAI_API_KEY=sk-************************************************
|
||||
NEXT_PUBLIC_OPENAI_ASSISTANT_KEY=asst_************************
|
||||
|
||||
# Update these with your Supabase details from your project settings > API
|
||||
SUPABASE_SERVICE_ROLE_KEY=***************************************************************************************************************************************************************************************************************************
|
||||
|
||||
# Update these with your Stripe credentials from https://dashboard.stripe.com/apikeys
|
||||
# NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_***************************************************************************************************
|
||||
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_***************************************************************************************************
|
||||
# STRIPE_SECRET_KEY=sk_live_***************************************************************************************************
|
||||
STRIPE_SECRET_KEY=sk_test_***************************************************************************************************
|
||||
# The commented variable is usually for production webhook key. This you get in the Stripe dashboard and is usually shorter.
|
||||
# STRIPE_WEBHOOK_SECRET=whsec_********************************
|
||||
STRIPE_WEBHOOK_SECRET=whsec_****************************************************************
|
||||
|
||||
# Update this with your stable site URL only for the production environment.
|
||||
# NEXT_PUBLIC_SITE_URL=https://horizon-ui.com/shadcn-nextjs-boilerplate
|
||||
# NEXT_PUBLIC_SITE_URL=https://******************.com
|
||||
|
||||
NEXT_PUBLIC_AWS_S3_REGION=eu-north-1
|
||||
NEXT_PUBLIC_AWS_S3_ACCESS_KEY_ID=********************
|
||||
NEXT_PUBLIC_AWS_S3_SECRET_ACCESS_KEY=****************************************
|
||||
NEXT_PUBLIC_AWS_S3_BUCKET_NAME=mybucket
|
||||
3
boilerplate-chakra-pro-main/.eslintrc.json
Normal file
3
boilerplate-chakra-pro-main/.eslintrc.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "next/core-web-vitals"
|
||||
}
|
||||
@@ -3,12 +3,7 @@
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
@@ -30,8 +25,14 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local.production
|
||||
.env.local.new
|
||||
.env.local.non-docker
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
@@ -39,3 +40,8 @@ yarn-error.log*
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
yarn.lock
|
||||
|
||||
# editors
|
||||
.vscode
|
||||
0
boilerplate-chakra-pro-main/.npmignore
Normal file
0
boilerplate-chakra-pro-main/.npmignore
Normal file
3
boilerplate-chakra-pro-main/.npmrc
Normal file
3
boilerplate-chakra-pro-main/.npmrc
Normal file
@@ -0,0 +1,3 @@
|
||||
legacy-peer-deps=true
|
||||
auto-install-peers=true
|
||||
strict-peer-dependencies=false
|
||||
106
boilerplate-chakra-pro-main/README.md
Normal file
106
boilerplate-chakra-pro-main/README.md
Normal file
@@ -0,0 +1,106 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="https://horizon-ui.com/boilerplate" target="_blank">
|
||||
<img src="https://i.ibb.co/LYGWM0W/top-boilerplate-readme.png" alt="Horizon UI Boilerplate - NextJS Template for Startups & Companies" width="400px" max-width="400px">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a style="color: #4318FF;" target="_blank" href="https://horizon-ui.com/boilerplate">Website</a> •
|
||||
<a style="color: #4318FF;" target="_blank" href="https://horizon-ui.com/docs-boilerplate/">Documentation</a> •
|
||||
<a style="color: #4318FF;" target="_blank" href="https://horizon-ui.com/pro">Admin Template</a> •
|
||||
<a style="color: #4318FF;" target="_blank" href="https://horizon-ui.com/ai-template">AI Template</a> •
|
||||
<a style="color: #4318FF;" target="_blank" href="https://twitter.com/horizon_ui">Twitter</a>
|
||||
<p align="center" style="max-width: 500px; margin: auto;">
|
||||
Launch your SaaS startup within a few days with the all-in-one NextJS boilerplate that you always searched for.
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<p align="center" style="width: 100%;">
|
||||
<a style="display:flex; justify-content: center; width: 100%;" href="https://horizon-ui.com/boilerplate" target="_blank"><img style="border-radius: 10px; width: 100%;" src="https://i.ibb.co/G0xrhsk/horizon-boilerplate-image-readme.png" alt="Horizon UI Free Tailwind CSS Landing Page Kit" /></a>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
|
||||
# Quickstart install
|
||||
|
||||
Install our product by running either of the following:
|
||||
|
||||
- Open the .zip archive file you got when you bought Horizon
|
||||
|
||||
- Install NodeJS LTS from [NodeJs Official Page](https://nodejs.org/en/) (NOTE: Product only works with LTS version).
|
||||
|
||||
|
||||
Run in the terminal this command:
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
<br />
|
||||
|
||||
Then run this command to start your local server:
|
||||
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
|
||||
|
||||
# Documentation
|
||||
|
||||
View <a href="https://horizon-ui.com/docs-boilerplate/" target="_blank">full documentation here</a>
|
||||
|
||||
|
||||
---
|
||||
|
||||
# Example Sections
|
||||
|
||||
If you want to get inspiration for your startup project or just show something directly to your clients, you can jump-start your development with our pre-built example sections. You will be able to quickly set up the basic structure for your web project.
|
||||
|
||||
View <a href="https://horizon-ui.com/boilerplate#pages" target="_blank">example sections here</a>
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
# Reporting Issues
|
||||
|
||||
We use GitHub Issues as the official bug tracker for the Horizon UI. Here are
|
||||
some advice for our users who want to report an issue:
|
||||
|
||||
1. Make sure that you are using the latest version of the Horizon UI Boilerplate. Check the CHANGELOG for your dashboard on our [CHANGE LOG File](https://github.com/horizon-ui/boilerplate-issues/blob/main/CHANGELOG.md).
|
||||
<br />
|
||||
|
||||
1. Providing us with reproducible steps for the issue will shorten the time it takes for it to be fixed.
|
||||
<br />
|
||||
|
||||
|
||||
3. Some issues may be browser-specific, so specifying in what browser you encountered the issue might help.
|
||||
|
||||
---
|
||||
|
||||
# Community
|
||||
|
||||
Connect with the community! Feel free to ask questions, report issues, and meet new people who already use Horizon UI!
|
||||
|
||||
💬 [Join the #HorizonUI Discord Community!](https://discord.gg/f6tEKFBd4m)
|
||||
|
||||
|
||||
### Copyright and license
|
||||
|
||||
⭐️ [Copyright 2024 Horizon UI](https://www.horizon-ui.com/?ref=readme-horizon)
|
||||
|
||||
📄 [Horizon UI License](https://horizon-ui.notion.site/End-User-License-Agreement-8fb09441ea8c4c08b60c37996195a6d5)
|
||||
|
||||
|
||||
---
|
||||
|
||||
# Credits
|
||||
|
||||
Special thanks to the open-source resources that helped us create this awesome boilerplate package, including:
|
||||
|
||||
- [NextJS Subscription Payments](https://github.com/vercel/nextjs-subscription-payments)
|
||||
- [ChatBot UI by mckaywrigley](https://github.com/mckaywrigley/chatbot-ui)
|
||||
17
boilerplate-chakra-pro-main/app-essay-builder.code-workspace
Normal file
17
boilerplate-chakra-pro-main/app-essay-builder.code-workspace
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"folders": [
|
||||
{
|
||||
"name": "project-root",
|
||||
"path": "./"
|
||||
},
|
||||
{
|
||||
"name": "supabase-functions",
|
||||
"path": "supabase/functions"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"files.exclude": {
|
||||
"supabase/functions/": true
|
||||
}
|
||||
}
|
||||
}
|
||||
43
boilerplate-chakra-pro-main/app/api/chatAPI/route.ts
Normal file
43
boilerplate-chakra-pro-main/app/api/chatAPI/route.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { ChatBody } from '@/types/types';
|
||||
import { OpenAIStream } from '@/utils/streams/chatStream';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
export async function GET(req: Request): Promise<Response> {
|
||||
try {
|
||||
const { inputMessage, model, apiKey } = (await req.json()) as ChatBody;
|
||||
|
||||
let apiKeyFinal;
|
||||
if (apiKey) {
|
||||
apiKeyFinal = apiKey;
|
||||
} else {
|
||||
apiKeyFinal = process.env.NEXT_PUBLIC_OPENAI_API_KEY;
|
||||
}
|
||||
|
||||
const stream = await OpenAIStream(inputMessage, model, apiKeyFinal);
|
||||
|
||||
return new Response(stream);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return new Response('Error', { status: 500 });
|
||||
}
|
||||
}
|
||||
export async function POST(req: Request): Promise<Response> {
|
||||
try {
|
||||
const { inputMessage, model, apiKey } = (await req.json()) as ChatBody;
|
||||
|
||||
let apiKeyFinal;
|
||||
if (apiKey) {
|
||||
apiKeyFinal = apiKey;
|
||||
} else {
|
||||
apiKeyFinal = process.env.NEXT_PUBLIC_OPENAI_API_KEY;
|
||||
}
|
||||
|
||||
const stream = await OpenAIStream(inputMessage, model, apiKeyFinal);
|
||||
|
||||
return new Response(stream);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return new Response('Error', { status: 500 });
|
||||
}
|
||||
}
|
||||
29
boilerplate-chakra-pro-main/app/api/essayAPI/route.ts
Normal file
29
boilerplate-chakra-pro-main/app/api/essayAPI/route.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { EssayBody } from '@/types/types';
|
||||
import { OpenAIStream } from '@/utils/streams/essayStream';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
const handler = async (req: Request): Promise<Response> => {
|
||||
try {
|
||||
const {
|
||||
topic,
|
||||
words,
|
||||
essayType,
|
||||
model,
|
||||
apiKey
|
||||
} = (await req.json()) as EssayBody;
|
||||
|
||||
if (!apiKey) {
|
||||
return new Response('API key not found', { status: 500 });
|
||||
}
|
||||
|
||||
const stream = await OpenAIStream(topic, essayType, words, model, apiKey);
|
||||
|
||||
return new Response(stream);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return new Response('Error', { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
export default handler;
|
||||
43
boilerplate-chakra-pro-main/app/api/premiumEssayAPI/route.ts
Normal file
43
boilerplate-chakra-pro-main/app/api/premiumEssayAPI/route.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { PremiumEssayBody } from '@/types/types';
|
||||
import { OpenAIStream } from '@/utils/streams/premiumEssayStream';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
const handler = async (req: Request): Promise<Response> => {
|
||||
try {
|
||||
const {
|
||||
words,
|
||||
topic,
|
||||
essayType,
|
||||
tone,
|
||||
citation,
|
||||
level,
|
||||
citations,
|
||||
model,
|
||||
apiKey
|
||||
} = (await req.json()) as PremiumEssayBody;
|
||||
|
||||
if (!apiKey) {
|
||||
return new Response('API key not found', { status: 500 });
|
||||
}
|
||||
|
||||
const stream = await OpenAIStream(
|
||||
words,
|
||||
topic,
|
||||
essayType,
|
||||
tone,
|
||||
citation,
|
||||
level,
|
||||
citations,
|
||||
model,
|
||||
apiKey
|
||||
);
|
||||
|
||||
return new Response(stream);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return new Response('Error', { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
export default handler;
|
||||
81
boilerplate-chakra-pro-main/app/api/webhooks/route.ts
Normal file
81
boilerplate-chakra-pro-main/app/api/webhooks/route.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { stripe } from '@/utils/stripe/config';
|
||||
import {
|
||||
manageSubscriptionStatusChange,
|
||||
upsertPriceRecord,
|
||||
upsertProductRecord
|
||||
} from '@/utils/supabase/admin';
|
||||
import { headers } from 'next/headers';
|
||||
import Stripe from 'stripe';
|
||||
|
||||
const relevantEvents = new Set([
|
||||
'product.created',
|
||||
'product.updated',
|
||||
'price.created',
|
||||
'price.updated',
|
||||
'checkout.session.completed',
|
||||
'customer.subscription.created',
|
||||
'customer.subscription.updated',
|
||||
'customer.subscription.deleted'
|
||||
]);
|
||||
|
||||
export async function POST(req: Request) {
|
||||
const body = await req.text();
|
||||
const sig = headers().get('Stripe-Signature') as string;
|
||||
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
|
||||
let event: Stripe.Event;
|
||||
|
||||
try {
|
||||
if (!sig || !webhookSecret) return;
|
||||
event = stripe.webhooks.constructEvent(body, sig, webhookSecret);
|
||||
} catch (err) {
|
||||
console.log(`❌ Error message: ${err.message}`);
|
||||
return new Response(`Webhook Error: ${err.message}`, { status: 400 });
|
||||
}
|
||||
|
||||
if (relevantEvents.has(event.type)) {
|
||||
try {
|
||||
switch (event.type) {
|
||||
case 'product.created':
|
||||
case 'product.updated':
|
||||
await upsertProductRecord(event.data.object as Stripe.Product);
|
||||
break;
|
||||
case 'price.created':
|
||||
case 'price.updated':
|
||||
await upsertPriceRecord(event.data.object as Stripe.Price);
|
||||
break;
|
||||
case 'customer.subscription.created':
|
||||
case 'customer.subscription.updated':
|
||||
case 'customer.subscription.deleted':
|
||||
const subscription = event.data.object as Stripe.Subscription;
|
||||
await manageSubscriptionStatusChange(
|
||||
subscription.id,
|
||||
subscription.customer as string,
|
||||
event.type === 'customer.subscription.created'
|
||||
);
|
||||
break;
|
||||
case 'checkout.session.completed':
|
||||
const checkoutSession = event.data.object as Stripe.Checkout.Session;
|
||||
if (checkoutSession.mode === 'subscription') {
|
||||
const subscriptionId = checkoutSession.subscription;
|
||||
await manageSubscriptionStatusChange(
|
||||
subscriptionId as string,
|
||||
checkoutSession.customer as string,
|
||||
true
|
||||
);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unhandled relevant event!');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return new Response(
|
||||
'Webhook handler failed. View your nextjs function logs.',
|
||||
{
|
||||
status: 400
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
return new Response(JSON.stringify({ received: true }));
|
||||
}
|
||||
35
boilerplate-chakra-pro-main/app/auth/callback/route.ts
Normal file
35
boilerplate-chakra-pro-main/app/auth/callback/route.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { getErrorRedirect, getStatusRedirect } from '@/utils/helpers';
|
||||
import { createClient } from '@/utils/supabase/server';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
// The `/auth/callback` route is required for the server-side auth flow implemented
|
||||
// by the `@supabase/ssr` package. It exchanges an auth code for the user's session.
|
||||
const requestUrl = new URL(request.url);
|
||||
const code = requestUrl.searchParams.get('code');
|
||||
|
||||
if (code) {
|
||||
const supabase = createClient();
|
||||
|
||||
const { error } = await supabase.auth.exchangeCodeForSession(code);
|
||||
|
||||
if (error) {
|
||||
return NextResponse.redirect(
|
||||
getErrorRedirect(
|
||||
`${requestUrl.origin}/dashboard/signin`,
|
||||
error.name,
|
||||
"Sorry, we weren't able to log you in. Please try again."
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// URL to redirect to after sign in process completes
|
||||
return NextResponse.redirect(
|
||||
getStatusRedirect(
|
||||
`${requestUrl.origin}/dashboard/main`,
|
||||
'Success!',
|
||||
'You are now signed in.'
|
||||
)
|
||||
);
|
||||
}
|
||||
36
boilerplate-chakra-pro-main/app/auth/reset_password/route.ts
Normal file
36
boilerplate-chakra-pro-main/app/auth/reset_password/route.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { createClient } from '@/utils/supabase/server';
|
||||
import { NextResponse } from 'next/server';
|
||||
import { NextRequest } from 'next/server';
|
||||
import { getErrorRedirect, getStatusRedirect } from '@/utils/helpers';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
// The `/auth/callback` route is required for the server-side auth flow implemented
|
||||
// by the `@supabase/ssr` package. It exchanges an auth code for the user's session.
|
||||
const requestUrl = new URL(request.url);
|
||||
const code = requestUrl.searchParams.get('code');
|
||||
|
||||
if (code) {
|
||||
const supabase = createClient();
|
||||
|
||||
const { error } = await supabase.auth.exchangeCodeForSession(code);
|
||||
|
||||
if (error) {
|
||||
return NextResponse.redirect(
|
||||
getErrorRedirect(
|
||||
`${requestUrl.origin}/signin/forgot_password`,
|
||||
error.name,
|
||||
"Sorry, we weren't able to log you in. Please try again."
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// URL to redirect to after sign in process completes
|
||||
return NextResponse.redirect(
|
||||
getStatusRedirect(
|
||||
`${requestUrl.origin}/signin/update_password`,
|
||||
'You are now signed in.',
|
||||
'Please enter a new password for your account.'
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import Assistant from '@/components/dashboard/ai-assistant';
|
||||
import { Providers } from '@/components/providers';
|
||||
import {
|
||||
getProducts,
|
||||
getSubscription,
|
||||
getUser,
|
||||
getUserDetails
|
||||
} from '@/utils/supabase/queries';
|
||||
import { createClient } from '@/utils/supabase/server';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export default async function AiAssistant() {
|
||||
const supabase = createClient();
|
||||
const [user, userDetails, products, subscription] = await Promise.all([
|
||||
getUser(supabase),
|
||||
getUserDetails(supabase),
|
||||
getProducts(supabase),
|
||||
getSubscription(supabase)
|
||||
]);
|
||||
|
||||
if (!user) {
|
||||
return redirect('/dashboard/signin');
|
||||
}
|
||||
|
||||
return (
|
||||
<Providers>
|
||||
<Assistant
|
||||
userDetails={userDetails}
|
||||
user={user}
|
||||
products={products}
|
||||
subscription={subscription}
|
||||
/>
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
36
boilerplate-chakra-pro-main/app/dashboard/ai-chat/page.tsx
Normal file
36
boilerplate-chakra-pro-main/app/dashboard/ai-chat/page.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
getProducts,
|
||||
getSubscription,
|
||||
getUser,
|
||||
getUserDetails
|
||||
} from '@/utils/supabase/queries';
|
||||
|
||||
import Chat from '@/components/dashboard/ai-chat';
|
||||
import { Providers } from '@/components/providers';
|
||||
import { createClient } from '@/utils/supabase/server';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export default async function AiChat() {
|
||||
const supabase = createClient();
|
||||
const [user, userDetails, products, subscription] = await Promise.all([
|
||||
getUser(supabase),
|
||||
getUserDetails(supabase),
|
||||
getProducts(supabase),
|
||||
getSubscription(supabase)
|
||||
]);
|
||||
|
||||
if (!user) {
|
||||
return redirect('/dashboard/signin');
|
||||
}
|
||||
|
||||
return (
|
||||
<Providers>
|
||||
<Chat
|
||||
userDetails={userDetails}
|
||||
user={user}
|
||||
products={products}
|
||||
subscription={subscription}
|
||||
/>
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import Generator from '@/components/dashboard/ai-generator';
|
||||
import { Providers } from '@/components/providers';
|
||||
import {
|
||||
getProducts,
|
||||
getSubscription,
|
||||
getUser,
|
||||
getUserDetails
|
||||
} from '@/utils/supabase/queries';
|
||||
import { createClient } from '@/utils/supabase/server';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export default async function AiGenerator() {
|
||||
const supabase = createClient();
|
||||
const [user, userDetails, products, subscription] = await Promise.all([
|
||||
getUser(supabase),
|
||||
getUserDetails(supabase),
|
||||
getProducts(supabase),
|
||||
getSubscription(supabase)
|
||||
]);
|
||||
|
||||
if (!user) {
|
||||
return redirect('/dashboard/signin');
|
||||
}
|
||||
|
||||
return (
|
||||
<Providers>
|
||||
<Generator
|
||||
userDetails={userDetails}
|
||||
user={user}
|
||||
products={products}
|
||||
subscription={subscription}
|
||||
/>
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
35
boilerplate-chakra-pro-main/app/dashboard/main/page.tsx
Normal file
35
boilerplate-chakra-pro-main/app/dashboard/main/page.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Providers } from '@/components/providers';
|
||||
|
||||
import Main from '@/components/dashboard/main';
|
||||
import {
|
||||
getProducts,
|
||||
getSubscription,
|
||||
getUser,
|
||||
getUserDetails
|
||||
} from '@/utils/supabase/queries';
|
||||
import { createClient } from '@/utils/supabase/server';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export default async function MainPage() {
|
||||
const supabase = createClient();
|
||||
const [user, userDetails, products, subscription] = await Promise.all([
|
||||
getUser(supabase),
|
||||
getUserDetails(supabase),
|
||||
getProducts(supabase),
|
||||
getSubscription(supabase)
|
||||
]);
|
||||
if (!user) {
|
||||
return redirect('/dashboard/signin');
|
||||
}
|
||||
|
||||
return (
|
||||
<Providers>
|
||||
<Main
|
||||
userDetails={userDetails}
|
||||
user={user}
|
||||
products={products}
|
||||
subscription={subscription}
|
||||
/>
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
14
boilerplate-chakra-pro-main/app/dashboard/page.tsx
Normal file
14
boilerplate-chakra-pro-main/app/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,42 @@
|
||||
import PremiumGenerator from '@/components/dashboard/premium-generator';
|
||||
import { Providers } from '@/components/providers';
|
||||
import {
|
||||
getProducts,
|
||||
getSubscription,
|
||||
getUser,
|
||||
getUserDetails
|
||||
} from '@/utils/supabase/queries';
|
||||
import { createClient } from '@/utils/supabase/server';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export default async function PremiumGeneratorPage() {
|
||||
const supabase = createClient();
|
||||
const [user, userDetails, products, subscription] = await Promise.all([
|
||||
getUser(supabase),
|
||||
getUserDetails(supabase),
|
||||
getProducts(supabase),
|
||||
getSubscription(supabase)
|
||||
]);
|
||||
|
||||
if (!user) {
|
||||
return redirect('/dashboard/signin');
|
||||
}
|
||||
|
||||
if (!subscription) {
|
||||
redirect('/dashboard/main');
|
||||
}
|
||||
return (
|
||||
<Providers>
|
||||
{subscription ? (
|
||||
<PremiumGenerator
|
||||
userDetails={userDetails}
|
||||
user={user}
|
||||
products={products}
|
||||
subscription={subscription}
|
||||
/>
|
||||
) : (
|
||||
<p>NICE TRY BUDDY</p>
|
||||
)}
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
35
boilerplate-chakra-pro-main/app/dashboard/settings/page.tsx
Normal file
35
boilerplate-chakra-pro-main/app/dashboard/settings/page.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import Settings from '@/components/dashboard/settings';
|
||||
import { Providers } from '@/components/providers';
|
||||
import {
|
||||
getProducts,
|
||||
getSubscription,
|
||||
getUser,
|
||||
getUserDetails
|
||||
} from '@/utils/supabase/queries';
|
||||
import { createClient } from '@/utils/supabase/server';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export default async function SettingsPage() {
|
||||
const supabase = createClient();
|
||||
const [user, userDetails, products, subscription] = await Promise.all([
|
||||
getUser(supabase),
|
||||
getUserDetails(supabase),
|
||||
getProducts(supabase),
|
||||
getSubscription(supabase)
|
||||
]);
|
||||
|
||||
if (!user) {
|
||||
return redirect('/dashboard/signin');
|
||||
}
|
||||
|
||||
return (
|
||||
<Providers>
|
||||
<Settings
|
||||
userDetails={userDetails}
|
||||
user={user}
|
||||
products={products}
|
||||
subscription={subscription}
|
||||
/>
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import DefaultAuth from '@/components/auth';
|
||||
import AuthUI from '@/components/auth/AuthUI';
|
||||
import { Providers } from '@/components/providers';
|
||||
import {
|
||||
getAuthTypes,
|
||||
getDefaultSignInView,
|
||||
getRedirectMethod,
|
||||
getViewTypes
|
||||
} from '@/utils/auth-helpers/settings';
|
||||
import { createClient } from '@/utils/supabase/server';
|
||||
import { cookies } from 'next/headers';
|
||||
import { redirect } from 'next/navigation';
|
||||
import illustration from '/public/img/auth/auth.png';
|
||||
|
||||
export default async function SignIn({
|
||||
params,
|
||||
searchParams
|
||||
}: {
|
||||
params: { id: string };
|
||||
searchParams: { disable_button: boolean };
|
||||
}) {
|
||||
const { allowOauth, allowEmail, allowPassword } = getAuthTypes();
|
||||
const viewTypes = getViewTypes();
|
||||
const redirectMethod = getRedirectMethod();
|
||||
|
||||
// Declare 'viewProp' and initialize with the default value
|
||||
let viewProp: string;
|
||||
|
||||
// Assign url id to 'viewProp' if it's a valid string and ViewTypes includes it
|
||||
if (typeof params.id === 'string' && viewTypes.includes(params.id)) {
|
||||
viewProp = params.id;
|
||||
} else {
|
||||
const preferredSignInView =
|
||||
cookies().get('preferredSignInView')?.value || null;
|
||||
viewProp = getDefaultSignInView(preferredSignInView);
|
||||
return redirect(`/dashboard/signin/${viewProp}`);
|
||||
}
|
||||
|
||||
// Check if the user is already logged in and redirect to the account page if so
|
||||
const supabase = createClient();
|
||||
|
||||
const {
|
||||
data: { user }
|
||||
} = await supabase.auth.getUser();
|
||||
|
||||
if (user && viewProp !== 'update_password') {
|
||||
return redirect('/dashboard/main');
|
||||
} else if (!user && viewProp === 'update_password') {
|
||||
return redirect('/dashboard/signin');
|
||||
}
|
||||
return (
|
||||
<Providers>
|
||||
<DefaultAuth
|
||||
viewProp={viewProp}
|
||||
illustrationBackground={illustration.src}
|
||||
>
|
||||
<div>
|
||||
<AuthUI
|
||||
viewProp={viewProp}
|
||||
user={user}
|
||||
allowPassword={allowPassword}
|
||||
allowEmail={allowEmail}
|
||||
redirectMethod={redirectMethod}
|
||||
disableButton={searchParams.disable_button}
|
||||
allowOauth={allowOauth}
|
||||
/>
|
||||
</div>
|
||||
</DefaultAuth>
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
11
boilerplate-chakra-pro-main/app/dashboard/signin/page.tsx
Normal file
11
boilerplate-chakra-pro-main/app/dashboard/signin/page.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { getDefaultSignInView } from '@/utils/auth-helpers/settings';
|
||||
import { cookies } from 'next/headers';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export default function SignIn() {
|
||||
const preferredSignInView =
|
||||
cookies().get('preferredSignInView')?.value || null;
|
||||
const defaultView = getDefaultSignInView(preferredSignInView);
|
||||
|
||||
return redirect(`/dashboard/signin/${defaultView}`);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import Subscription from '@/components/dashboard/subscription';
|
||||
import { Providers } from '@/components/providers';
|
||||
import {
|
||||
getProducts,
|
||||
getSubscription,
|
||||
getUser,
|
||||
getUserDetails
|
||||
} from '@/utils/supabase/queries';
|
||||
import { createClient } from '@/utils/supabase/server';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export default async function SubscriptionPage() {
|
||||
const supabase = createClient();
|
||||
const [user, userDetails, products, subscription] = await Promise.all([
|
||||
getUser(supabase),
|
||||
getUserDetails(supabase),
|
||||
getProducts(supabase),
|
||||
getSubscription(supabase)
|
||||
]);
|
||||
|
||||
if (!user) {
|
||||
return redirect('/dashboard/signin');
|
||||
}
|
||||
return (
|
||||
<Providers>
|
||||
<Subscription
|
||||
userDetails={userDetails}
|
||||
user={user}
|
||||
products={products}
|
||||
subscription={subscription}
|
||||
/>
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import UsersList from '@/components/dashboard/users-list';
|
||||
import { Providers } from '@/components/providers';
|
||||
import {
|
||||
getProducts,
|
||||
getSubscription,
|
||||
getUser,
|
||||
getUserDetails
|
||||
} from '@/utils/supabase/queries';
|
||||
import { createClient } from '@/utils/supabase/server';
|
||||
import { redirect } from 'next/navigation';
|
||||
|
||||
export default async function UserList() {
|
||||
const supabase = createClient();
|
||||
const [user, userDetails, products, subscription] = await Promise.all([
|
||||
getUser(supabase),
|
||||
getUserDetails(supabase),
|
||||
getProducts(supabase),
|
||||
getSubscription(supabase)
|
||||
]);
|
||||
|
||||
if (!user) {
|
||||
return redirect('/dashboard/signin');
|
||||
}
|
||||
|
||||
return (
|
||||
<Providers>
|
||||
<UsersList
|
||||
userDetails={userDetails}
|
||||
user={user}
|
||||
products={products}
|
||||
subscription={subscription}
|
||||
/>
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
29
boilerplate-chakra-pro-main/app/essayAPI.ts
Normal file
29
boilerplate-chakra-pro-main/app/essayAPI.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { EssayBody } from '@/types/types';
|
||||
import { OpenAIStream } from '@/utils/streams/essayStream';
|
||||
|
||||
export const runtime = 'edge';
|
||||
|
||||
const handler = async (req: Request): Promise<Response> => {
|
||||
try {
|
||||
const {
|
||||
topic,
|
||||
words,
|
||||
essayType,
|
||||
model,
|
||||
apiKey
|
||||
} = (await req.json()) as EssayBody;
|
||||
|
||||
if (!apiKey) {
|
||||
return new Response('API key not found', { status: 500 });
|
||||
}
|
||||
|
||||
const stream = await OpenAIStream(topic, essayType, words, model, apiKey);
|
||||
|
||||
return new Response(stream);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return new Response('Error', { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
export default handler;
|
||||
81
boilerplate-chakra-pro-main/app/layout.tsx
Normal file
81
boilerplate-chakra-pro-main/app/layout.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import SupabaseProvider from './supabase-provider';
|
||||
import Script from 'next/script';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import '@/styles/globals.css';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
export default function RootLayout({
|
||||
// Layouts must accept a children prop.
|
||||
// This will be populated with nested layouts or pages
|
||||
children,
|
||||
}: PropsWithChildren) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>
|
||||
Horizon UI Boilerplate - Launch your startup project 10X in a few
|
||||
moments - The best NextJS Boilerplate (This is an example)
|
||||
</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
{/* <!-- Social tags --> */}
|
||||
<meta
|
||||
name="keywords"
|
||||
content="Add here your main keywords and separate them with a comma"
|
||||
/>
|
||||
<meta name="description" content="Add here your website description" />
|
||||
{/* <!-- Schema.org markup for Google+ --> */}
|
||||
<meta itemProp="name" content="Add here your website name / title" />
|
||||
<meta
|
||||
itemProp="description"
|
||||
content="Add here your website description"
|
||||
/>
|
||||
<meta
|
||||
itemProp="image"
|
||||
content="Add here the link for your website SEO image"
|
||||
/>
|
||||
{/* <!-- Twitter Card data --> */}
|
||||
<meta name="twitter:card" content="product" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content="Add here your website name / title"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Add here your website description"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="Add here the link for your website SEO image"
|
||||
/>
|
||||
{/* <!-- Open Graph data --> */}
|
||||
<meta
|
||||
property="og:title"
|
||||
content="Add here your website name / title"
|
||||
/>
|
||||
<meta property="og:type" content="product" />
|
||||
<meta property="og:url" content="https://your-website.com" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="Add here the link for your website SEO image"
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Add here your website description"
|
||||
/>
|
||||
<meta
|
||||
property="og:site_name"
|
||||
content="Add here your website name / title"
|
||||
/>
|
||||
<link rel="canonical" href="https://your-website.com" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</head>
|
||||
<body style={{ background: 'white' }}>
|
||||
<SupabaseProvider>
|
||||
{/* @ts-ignore */}
|
||||
<main id="skip">{children}</main>
|
||||
</SupabaseProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
10
boilerplate-chakra-pro-main/app/page.tsx
Normal file
10
boilerplate-chakra-pro-main/app/page.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import Landing from '@/components/landing';
|
||||
import { Providers } from '@/components/providers';
|
||||
|
||||
export default async function PricingPage() {
|
||||
return (
|
||||
<Providers>
|
||||
<Landing />
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
23
boilerplate-chakra-pro-main/app/pricing/page.tsx
Normal file
23
boilerplate-chakra-pro-main/app/pricing/page.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import Pricing from '@/components/pricing';
|
||||
import { Providers } from '@/components/providers';
|
||||
import {
|
||||
getProducts,
|
||||
getUser,
|
||||
getSubscription
|
||||
} from '@/utils/supabase/queries';
|
||||
import { createClient } from '@/utils/supabase/server';
|
||||
|
||||
export default async function PricingPage() {
|
||||
const supabase = createClient();
|
||||
const [user, products, subscription] = await Promise.all([
|
||||
getUser(supabase),
|
||||
getProducts(supabase),
|
||||
getSubscription(supabase)
|
||||
]);
|
||||
|
||||
return (
|
||||
<Providers>
|
||||
<Pricing user={user} products={products} subscription={subscription} />
|
||||
</Providers>
|
||||
);
|
||||
}
|
||||
50
boilerplate-chakra-pro-main/app/supabase-provider.tsx
Normal file
50
boilerplate-chakra-pro-main/app/supabase-provider.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
'use client';
|
||||
|
||||
import type { Database } from '@/types_db';
|
||||
import { createPagesBrowserClient } from '@supabase/auth-helpers-nextjs';
|
||||
import type { SupabaseClient } from '@supabase/auth-helpers-nextjs';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { createContext, useContext, useEffect, useState } from 'react';
|
||||
|
||||
type SupabaseContext = {
|
||||
supabase: SupabaseClient<Database>;
|
||||
};
|
||||
|
||||
const Context = createContext<SupabaseContext | undefined>(undefined);
|
||||
|
||||
export default function SupabaseProvider({
|
||||
children
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const [supabase] = useState(() => createPagesBrowserClient());
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const {
|
||||
data: { subscription }
|
||||
} = supabase.auth.onAuthStateChange((event) => {
|
||||
if (event === 'SIGNED_IN') router.refresh();
|
||||
});
|
||||
|
||||
return () => {
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
}, [router, supabase]);
|
||||
|
||||
return (
|
||||
<Context.Provider value={{ supabase }}>
|
||||
<>{children}</>
|
||||
</Context.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useSupabase = () => {
|
||||
const context = useContext(Context);
|
||||
|
||||
if (context === undefined) {
|
||||
throw new Error('useSupabase must be used inside SupabaseProvider');
|
||||
}
|
||||
|
||||
return context;
|
||||
};
|
||||
54
boilerplate-chakra-pro-main/app/supabase-server.ts
Normal file
54
boilerplate-chakra-pro-main/app/supabase-server.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Database } from '@/types_db';
|
||||
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
|
||||
import { cookies } from 'next/headers';
|
||||
import { cache } from 'react';
|
||||
|
||||
export const createServerSupabaseClient = cache(() =>
|
||||
createServerComponentClient<Database>({ cookies })
|
||||
);
|
||||
|
||||
export async function getUserDetails() {
|
||||
const supabase = createServerSupabaseClient();
|
||||
try {
|
||||
const { data: userDetails } = await supabase
|
||||
.from('users')
|
||||
.select('*')
|
||||
.single();
|
||||
return userDetails;
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSubscription() {
|
||||
const supabase = createServerSupabaseClient();
|
||||
try {
|
||||
const { data: subscription } = await supabase
|
||||
.from('subscriptions')
|
||||
.select('*, prices(*, products(*))')
|
||||
.in('status', ['trialing', 'active'])
|
||||
.maybeSingle()
|
||||
.throwOnError();
|
||||
return subscription;
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export const getActiveProductsWithPrices = async () => {
|
||||
const supabase = createServerSupabaseClient();
|
||||
const { data, error } = await supabase
|
||||
.from('products')
|
||||
.select('*, prices(*)')
|
||||
.eq('active', true)
|
||||
.eq('prices.active', true)
|
||||
.order('metadata->index')
|
||||
.order('unit_amount', { foreignTable: 'prices' });
|
||||
|
||||
if (error) {
|
||||
console.log(error.message);
|
||||
}
|
||||
return data ?? [];
|
||||
};
|
||||
26
boilerplate-chakra-pro-main/components/MessageBox/index.tsx
Normal file
26
boilerplate-chakra-pro-main/components/MessageBox/index.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Flex, useColorModeValue } from '@chakra-ui/react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
|
||||
export default function MessageBox(props: { output: string }) {
|
||||
const { output } = props;
|
||||
const textColor = useColorModeValue('#120F43', 'white');
|
||||
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.200');
|
||||
return (
|
||||
<Flex
|
||||
w="100%"
|
||||
p="15px 20px"
|
||||
border="1px solid"
|
||||
color={textColor}
|
||||
borderColor={borderColor}
|
||||
borderRadius="10px"
|
||||
minH="564px"
|
||||
fontSize="md"
|
||||
fontWeight="500"
|
||||
mb="28px"
|
||||
>
|
||||
<ReactMarkdown className="font-medium">
|
||||
{output ? output : 'Your generated response will appear here...'}
|
||||
</ReactMarkdown>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import Card from '@/components/card/Card';
|
||||
import { useColorModeValue } from '@chakra-ui/system';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
|
||||
export default function MessageBox(props: { output: string }) {
|
||||
const { output } = props;
|
||||
const bgColor = useColorModeValue('white', 'whiteAlpha.100');
|
||||
const textColor = useColorModeValue('#120F43', 'white');
|
||||
const borderWidth = useColorModeValue('1px', '0px');
|
||||
return (
|
||||
<Card
|
||||
display={output ? 'flex' : 'none'}
|
||||
p="22px"
|
||||
maxH="max-content"
|
||||
color={textColor}
|
||||
bg={bgColor}
|
||||
backdropBlur="xl"
|
||||
borderWidth={borderWidth}
|
||||
fontSize={{ base: 'sm', md: '16px' }}
|
||||
lineHeight={{ base: '24px', md: '26px' }}
|
||||
fontWeight={'500'}
|
||||
>
|
||||
<ReactMarkdown className="font-medium">
|
||||
{output ? output : ''}
|
||||
</ReactMarkdown>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
100
boilerplate-chakra-pro-main/components/auth-ui/EmailSignIn.tsx
Normal file
100
boilerplate-chakra-pro-main/components/auth-ui/EmailSignIn.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
'use client';
|
||||
|
||||
import { signInWithEmail } from '@/utils/auth-helpers/server';
|
||||
import { handleRequest } from '@/utils/auth-helpers/client';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
FormLabel,
|
||||
Input,
|
||||
useColorModeValue,
|
||||
Link
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
// Define prop type with allowPassword boolean
|
||||
interface EmailSignInProps {
|
||||
allowPassword: boolean;
|
||||
redirectMethod: string;
|
||||
disableButton?: boolean;
|
||||
}
|
||||
|
||||
export default function EmailSignIn({
|
||||
allowPassword,
|
||||
redirectMethod
|
||||
}: EmailSignInProps) {
|
||||
const router = redirectMethod === 'client' ? useRouter() : null;
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const textColor = useColorModeValue('navy.700', 'white');
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
setIsSubmitting(true); // Disable the button while the request is being handled
|
||||
await handleRequest(e, signInWithEmail, router);
|
||||
setIsSubmitting(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box mb={8}>
|
||||
<form onSubmit={(e) => handleSubmit(e)}>
|
||||
<FormLabel
|
||||
htmlFor="email"
|
||||
display="flex"
|
||||
ms="4px"
|
||||
fontSize="sm"
|
||||
fontWeight="500"
|
||||
color={textColor}
|
||||
mb="8px"
|
||||
>
|
||||
Email
|
||||
</FormLabel>
|
||||
<Input
|
||||
isRequired={true}
|
||||
variant="auth"
|
||||
fontSize="sm"
|
||||
ms={{ base: '0px', md: '0px' }}
|
||||
id="email"
|
||||
placeholder="name@example.com"
|
||||
type="email"
|
||||
name="email"
|
||||
mb="24px"
|
||||
fontWeight="500"
|
||||
size="lg"
|
||||
/>
|
||||
<Button
|
||||
fontSize="sm"
|
||||
variant="brand"
|
||||
fontWeight="500"
|
||||
w="100%"
|
||||
h="50"
|
||||
mb="24px"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
>
|
||||
Sign in
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
{allowPassword && (
|
||||
<Flex direction="column">
|
||||
<Link
|
||||
href="/dashboard/signin/password_signin"
|
||||
color={textColor}
|
||||
fontWeight="medium"
|
||||
>
|
||||
Sign in with email and password
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
href="/dashboard/signin/signup"
|
||||
color={textColor}
|
||||
fontWeight="medium"
|
||||
>
|
||||
Don't have an account? Sign up
|
||||
</Link>
|
||||
</Flex>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
'use client';
|
||||
import { requestPasswordUpdate } from '@/utils/auth-helpers/server';
|
||||
import { handleRequest } from '@/utils/auth-helpers/client';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
FormLabel,
|
||||
Input,
|
||||
useColorModeValue,
|
||||
Link
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
// Define prop type with allowEmail boolean
|
||||
interface ForgotPasswordProps {
|
||||
allowEmail: boolean;
|
||||
redirectMethod: string;
|
||||
disableButton?: boolean;
|
||||
}
|
||||
|
||||
export default function ForgotPassword({
|
||||
allowEmail,
|
||||
redirectMethod
|
||||
}: ForgotPasswordProps) {
|
||||
const router = redirectMethod === 'client' ? useRouter() : null;
|
||||
|
||||
const textColor = useColorModeValue('navy.700', 'white');
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
setIsSubmitting(true); // Disable the button while the request is being handled
|
||||
await handleRequest(e, requestPasswordUpdate, router);
|
||||
setIsSubmitting(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box mb={8}>
|
||||
<form onSubmit={(e) => handleSubmit(e)}>
|
||||
<FormLabel
|
||||
htmlFor="email"
|
||||
display="flex"
|
||||
ms="4px"
|
||||
fontSize="sm"
|
||||
fontWeight="500"
|
||||
color={textColor}
|
||||
mb="8px"
|
||||
>
|
||||
Email
|
||||
</FormLabel>
|
||||
<Input
|
||||
isRequired={true}
|
||||
variant="auth"
|
||||
fontSize="sm"
|
||||
ms={{ base: '0px', md: '0px' }}
|
||||
id="email"
|
||||
placeholder="name@example.com"
|
||||
type="email"
|
||||
name="email"
|
||||
mb="24px"
|
||||
fontWeight="500"
|
||||
size="lg"
|
||||
/>
|
||||
<Button
|
||||
fontSize="sm"
|
||||
variant="brand"
|
||||
fontWeight="500"
|
||||
w="100%"
|
||||
h="50"
|
||||
mb="24px"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
>
|
||||
Sign in
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<Flex direction="column">
|
||||
<Link
|
||||
href="/dashboard/signin/password_signin"
|
||||
color={textColor}
|
||||
fontWeight="medium"
|
||||
>
|
||||
Sign in with email and password
|
||||
</Link>
|
||||
{allowEmail && (
|
||||
<Link
|
||||
href="/dashboard/signin/email_signin"
|
||||
color={textColor}
|
||||
fontWeight="medium"
|
||||
>
|
||||
Sign in via magic link
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
href="/dashboard/signin/signup"
|
||||
color={textColor}
|
||||
fontWeight="medium"
|
||||
>
|
||||
Don't have an account? Sign up
|
||||
</Link>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
'use client';
|
||||
|
||||
|
||||
import { signInWithOAuth } from '@/utils/auth-helpers/client';
|
||||
import { type Provider } from '@supabase/supabase-js';
|
||||
import { FcGoogle } from "react-icons/fc";
|
||||
import { useState } from 'react';
|
||||
import { useColorModeValue } from '@chakra-ui/system';
|
||||
import { Box, Button, Icon, Input } from '@chakra-ui/react';
|
||||
import { IconType } from 'react-icons';
|
||||
|
||||
type OAuthProviders = {
|
||||
name: Provider;
|
||||
displayName: string;
|
||||
icon: IconType;
|
||||
};
|
||||
|
||||
export default function OauthSignIn() {
|
||||
// Chakra color mode
|
||||
const googleBg = useColorModeValue('secondaryGray.300', 'whiteAlpha.200');
|
||||
const googleText = useColorModeValue('navy.700', 'white');
|
||||
const googleHover = useColorModeValue(
|
||||
{ bg: 'gray.200' },
|
||||
{ bg: 'whiteAlpha.300' },
|
||||
);
|
||||
const googleActive = useColorModeValue(
|
||||
{ bg: 'secondaryGray.300' },
|
||||
{ bg: 'whiteAlpha.200' },
|
||||
);
|
||||
const oAuthProviders: OAuthProviders[] = [
|
||||
{
|
||||
name: 'google',
|
||||
displayName: 'Google',
|
||||
icon: FcGoogle
|
||||
}
|
||||
/* Add desired OAuth providers here */
|
||||
];
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
setIsSubmitting(true); // Disable the button while the request is being handled
|
||||
await signInWithOAuth(e);
|
||||
setIsSubmitting(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box mt="40px">
|
||||
{oAuthProviders.map((provider) => (
|
||||
<form
|
||||
key={provider.name}
|
||||
className="pb-2"
|
||||
onSubmit={(e) => handleSubmit(e)}
|
||||
>
|
||||
<Input type="hidden" name="provider" value={provider.name} />
|
||||
<Button
|
||||
fontSize="sm"
|
||||
me="0px"
|
||||
py="15px"
|
||||
h="50px"
|
||||
w="100%"
|
||||
borderRadius="16px"
|
||||
bg={googleBg}
|
||||
color={googleText}
|
||||
fontWeight="500"
|
||||
_hover={googleHover}
|
||||
_active={googleActive}
|
||||
_focus={googleActive}
|
||||
type="submit"
|
||||
>
|
||||
<Icon as={ provider.icon} w="20px" h="20px" me="10px" />
|
||||
{provider.displayName}
|
||||
</Button>
|
||||
</form>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
'use client';
|
||||
|
||||
import { signInWithPassword } from '@/utils/auth-helpers/server';
|
||||
import { handleRequest } from '@/utils/auth-helpers/client';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
FormLabel,
|
||||
Input,
|
||||
Link,
|
||||
useColorModeValue
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
// Define prop type with allowEmail boolean
|
||||
interface PasswordSignInProps {
|
||||
allowEmail: boolean;
|
||||
redirectMethod: string;
|
||||
}
|
||||
|
||||
export default function PasswordSignIn({
|
||||
allowEmail,
|
||||
redirectMethod
|
||||
}: PasswordSignInProps) {
|
||||
const router = redirectMethod === 'client' ? useRouter() : null;
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const textColor = useColorModeValue('navy.700', 'white');
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
setIsSubmitting(true); // Disable the button while the request is being handled
|
||||
await handleRequest(e, signInWithPassword, router);
|
||||
setIsSubmitting(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box mb="auto">
|
||||
<Box mb="8px">
|
||||
<form noValidate={true} onSubmit={(e) => handleSubmit(e)}>
|
||||
<FormLabel
|
||||
htmlFor="email"
|
||||
display="flex"
|
||||
ms="4px"
|
||||
fontWeight="500"
|
||||
color={textColor}
|
||||
mb="8px"
|
||||
>
|
||||
Email
|
||||
</FormLabel>
|
||||
<Input
|
||||
isRequired={true}
|
||||
variant="auth"
|
||||
fontSize="sm"
|
||||
ms={{ base: '0px', md: '0px' }}
|
||||
id="email"
|
||||
placeholder="name@example.com"
|
||||
type="email"
|
||||
name="email"
|
||||
autoCapitalize="none"
|
||||
autoComplete="email"
|
||||
autoCorrect="off"
|
||||
mb="24px"
|
||||
fontWeight="500"
|
||||
size="lg"
|
||||
/>
|
||||
<FormLabel
|
||||
htmlFor="password"
|
||||
display="flex"
|
||||
ms="4px"
|
||||
fontWeight="500"
|
||||
color={textColor}
|
||||
mb="8px"
|
||||
>
|
||||
Password
|
||||
</FormLabel>
|
||||
<Input
|
||||
isRequired={true}
|
||||
variant="auth"
|
||||
fontSize="sm"
|
||||
ms={{ base: '0px', md: '0px' }}
|
||||
id="password"
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
name="password"
|
||||
autoComplete="current-password"
|
||||
mb="24px"
|
||||
fontWeight="500"
|
||||
size="lg"
|
||||
/>
|
||||
<Button
|
||||
fontSize="sm"
|
||||
variant="brand"
|
||||
fontWeight="500"
|
||||
w="100%"
|
||||
h="50"
|
||||
mb="24px"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
>
|
||||
Sign in
|
||||
</Button>
|
||||
</form>
|
||||
</Box>
|
||||
<Flex direction="column">
|
||||
<Link
|
||||
href="/dashboard/signin/forgot_password"
|
||||
color={textColor}
|
||||
fontWeight="medium"
|
||||
>
|
||||
Forgot your password?
|
||||
</Link>
|
||||
{allowEmail && (
|
||||
<Link
|
||||
href="/dashboard/signin/email_signin"
|
||||
color={textColor}
|
||||
fontWeight="medium"
|
||||
>
|
||||
Sign in via magic link
|
||||
</Link>
|
||||
)}
|
||||
<Link
|
||||
href="/dashboard/signin/signup"
|
||||
color={textColor}
|
||||
fontWeight="medium"
|
||||
>
|
||||
Don't have an account? Sign up
|
||||
</Link>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
131
boilerplate-chakra-pro-main/components/auth-ui/Signup.tsx
Normal file
131
boilerplate-chakra-pro-main/components/auth-ui/Signup.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { signUp } from '@/utils/auth-helpers/server';
|
||||
import { handleRequest } from '@/utils/auth-helpers/client';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Flex,
|
||||
useColorModeValue,
|
||||
Link,
|
||||
FormLabel,
|
||||
Box,
|
||||
Input,
|
||||
Button
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
// Define prop type with allowEmail boolean
|
||||
interface SignUpProps {
|
||||
allowEmail: boolean;
|
||||
redirectMethod: string;
|
||||
}
|
||||
|
||||
export default function SignUp({ allowEmail, redirectMethod }: SignUpProps) {
|
||||
const router = redirectMethod === 'client' ? useRouter() : null;
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const textColor = useColorModeValue('navy.700', 'white');
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
setIsSubmitting(true); // Disable the button while the request is being handled
|
||||
await handleRequest(e, signUp, router);
|
||||
setIsSubmitting(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box mb="auto" mt="20px">
|
||||
<Box mb="8px">
|
||||
<form noValidate={true} onSubmit={(e) => handleSubmit(e)}>
|
||||
<FormLabel
|
||||
htmlFor="email"
|
||||
display="flex"
|
||||
ms="4px"
|
||||
fontWeight="500"
|
||||
color={textColor}
|
||||
mb="8px"
|
||||
>
|
||||
Email
|
||||
</FormLabel>
|
||||
<Input
|
||||
isRequired={true}
|
||||
variant="auth"
|
||||
fontSize="sm"
|
||||
ms={{ base: '0px', md: '0px' }}
|
||||
id="email"
|
||||
placeholder="name@example.com"
|
||||
type="email"
|
||||
name="email"
|
||||
autoCapitalize="none"
|
||||
autoComplete="email"
|
||||
autoCorrect="off"
|
||||
mb="24px"
|
||||
fontWeight="500"
|
||||
size="lg"
|
||||
/>
|
||||
<FormLabel
|
||||
htmlFor="password"
|
||||
display="flex"
|
||||
ms="4px"
|
||||
fontWeight="500"
|
||||
color={textColor}
|
||||
mb="8px"
|
||||
>
|
||||
Password
|
||||
</FormLabel>
|
||||
<Input
|
||||
isRequired={true}
|
||||
variant="auth"
|
||||
fontSize="sm"
|
||||
ms={{ base: '0px', md: '0px' }}
|
||||
id="password"
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
name="password"
|
||||
autoComplete="current-password"
|
||||
mb="24px"
|
||||
fontWeight="500"
|
||||
size="lg"
|
||||
/>
|
||||
<Button
|
||||
fontSize="sm"
|
||||
variant="brand"
|
||||
fontWeight="500"
|
||||
w="100%"
|
||||
h="50"
|
||||
mb="24px"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
>
|
||||
Sign in
|
||||
</Button>
|
||||
</form>
|
||||
</Box>
|
||||
|
||||
<Flex direction="column">
|
||||
<Link
|
||||
href="/dashboard/signin/forgot_password"
|
||||
color={textColor}
|
||||
fontWeight="medium"
|
||||
>
|
||||
Forgot your password?
|
||||
</Link>
|
||||
<Link
|
||||
href="/dashboard/signin/password_signin"
|
||||
color={textColor}
|
||||
fontWeight="medium"
|
||||
>
|
||||
Already have an account?
|
||||
</Link>
|
||||
{allowEmail && (
|
||||
<Link
|
||||
href="/dashboard/signin/email_signin"
|
||||
color={textColor}
|
||||
fontWeight="medium"
|
||||
>
|
||||
Sign in via magic link
|
||||
</Link>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
'use client';
|
||||
|
||||
import { updatePassword } from '@/utils/auth-helpers/server';
|
||||
import { handleRequest } from '@/utils/auth-helpers/client';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
FormLabel,
|
||||
Input,
|
||||
useColorModeValue
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
interface UpdatePasswordProps {
|
||||
redirectMethod: string;
|
||||
}
|
||||
|
||||
export default function UpdatePassword({
|
||||
redirectMethod
|
||||
}: UpdatePasswordProps) {
|
||||
const router = redirectMethod === 'client' ? useRouter() : null;
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const textColor = useColorModeValue('navy.700', 'white');
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
setIsSubmitting(true); // Disable the button while the request is being handled
|
||||
await handleRequest(e, updatePassword, router);
|
||||
setIsSubmitting(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box mb={8}>
|
||||
<form onSubmit={(e) => handleSubmit(e)}>
|
||||
<FormLabel
|
||||
htmlFor="password"
|
||||
display="flex"
|
||||
ms="4px"
|
||||
fontSize="sm"
|
||||
fontWeight="500"
|
||||
color={textColor}
|
||||
mb="8px"
|
||||
>
|
||||
New Password
|
||||
</FormLabel>
|
||||
<Input
|
||||
isRequired={true}
|
||||
variant="auth"
|
||||
fontSize="sm"
|
||||
ms={{ base: '0px', md: '0px' }}
|
||||
mb="24px"
|
||||
fontWeight="500"
|
||||
size="lg"
|
||||
id="password"
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
name="password"
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
<FormLabel
|
||||
htmlFor="password"
|
||||
display="flex"
|
||||
ms="4px"
|
||||
fontSize="sm"
|
||||
fontWeight="500"
|
||||
color={textColor}
|
||||
mb="8px"
|
||||
>
|
||||
Confirm New Password
|
||||
</FormLabel>
|
||||
<Input
|
||||
isRequired={true}
|
||||
variant="auth"
|
||||
fontSize="sm"
|
||||
ms={{ base: '0px', md: '0px' }}
|
||||
mb="24px"
|
||||
fontWeight="500"
|
||||
size="lg"
|
||||
id="passwordConfirm"
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
name="passwordConfirm"
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
<Button
|
||||
fontSize="sm"
|
||||
variant="brand"
|
||||
fontWeight="500"
|
||||
w="100%"
|
||||
h="50"
|
||||
mb="24px"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
>
|
||||
Sign in
|
||||
</Button>
|
||||
</form>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
81
boilerplate-chakra-pro-main/components/auth/AuthUI.tsx
Normal file
81
boilerplate-chakra-pro-main/components/auth/AuthUI.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
'use client';
|
||||
|
||||
import PasswordSignIn from '@/components/auth-ui/PasswordSignIn';
|
||||
import EmailSignIn from '@/components/auth-ui/EmailSignIn';
|
||||
import OauthSignIn from '@/components/auth-ui/OauthSignIn';
|
||||
import ForgotPassword from '@/components/auth-ui/ForgotPassword';
|
||||
import UpdatePassword from '@/components/auth-ui/UpdatePassword';
|
||||
import SignUp from '@/components/auth-ui/Signup';
|
||||
import { Flex, Text } from '@chakra-ui/react';
|
||||
import { HSeparator } from '../separator/Separator';
|
||||
|
||||
export default function AuthUI(props: any) {
|
||||
return (
|
||||
<Flex
|
||||
direction={'column'}
|
||||
my="auto"
|
||||
mt={{ base: '30px', md: '70px', lg: 'auto' }}
|
||||
maxW={{ md: 'full', lg: '420px' }}
|
||||
>
|
||||
<Text fontSize="32px" fontWeight={'bold'} color="gray.900">
|
||||
{props.viewProp === 'signup'
|
||||
? 'Sign Up'
|
||||
: props.viewProp === 'forgot_password'
|
||||
? 'Forgot Password'
|
||||
: props.viewProp === 'update_password'
|
||||
? 'Update Password'
|
||||
: props.viewProp === 'email_signin'
|
||||
? 'Email Sign In'
|
||||
: 'Sign In'}
|
||||
</Text>
|
||||
<Text fontSize="16px" color="gray.t00">
|
||||
{props.viewProp === 'signup'
|
||||
? 'Enter your email and password to sign up!'
|
||||
: props.viewProp === 'forgot_password'
|
||||
? 'Enter your email to get a passoword reset link!'
|
||||
: props.viewProp === 'update_password'
|
||||
? 'Choose a new password for your account!'
|
||||
: props.viewProp === 'email_signin'
|
||||
? 'Enter your email to get a magic link!'
|
||||
: 'Enter your email and password to sign in!'}
|
||||
</Text>
|
||||
{props.viewProp !== 'update_password' &&
|
||||
props.viewProp !== 'signup' &&
|
||||
props.allowOauth && (
|
||||
<>
|
||||
<OauthSignIn />
|
||||
<HSeparator my="20px" />
|
||||
</>
|
||||
)}
|
||||
{props.viewProp === 'password_signin' && (
|
||||
<PasswordSignIn
|
||||
allowEmail={props.allowEmail}
|
||||
redirectMethod={props.redirectMethod}
|
||||
/>
|
||||
)}
|
||||
{props.viewProp === 'email_signin' && (
|
||||
<EmailSignIn
|
||||
allowPassword={props.allowPassword}
|
||||
redirectMethod={props.redirectMethod}
|
||||
disableButton={props.disableButton}
|
||||
/>
|
||||
)}
|
||||
{props.viewProp === 'forgot_password' && (
|
||||
<ForgotPassword
|
||||
allowEmail={props.allowEmail}
|
||||
redirectMethod={props.redirectMethod}
|
||||
disableButton={props.disableButton}
|
||||
/>
|
||||
)}
|
||||
{props.viewProp === 'update_password' && (
|
||||
<UpdatePassword redirectMethod={props.redirectMethod} />
|
||||
)}
|
||||
{props.viewProp === 'signup' && (
|
||||
<SignUp
|
||||
allowEmail={props.allowEmail}
|
||||
redirectMethod={props.redirectMethod}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
79
boilerplate-chakra-pro-main/components/auth/index.tsx
Normal file
79
boilerplate-chakra-pro-main/components/auth/index.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
'use client';
|
||||
|
||||
import Footer from '@/components/footer/FooterAuthDefault';
|
||||
import NavLink from '@/components/link/NavLink';
|
||||
import { Box, Flex, Icon, Link, Text } from '@chakra-ui/react';
|
||||
import { PropsWithChildren } from 'react';
|
||||
import { FaChevronLeft } from 'react-icons/fa';
|
||||
interface DefaultAuthLayoutProps extends PropsWithChildren {
|
||||
children: JSX.Element;
|
||||
illustrationBackground: string;
|
||||
viewProp: any;
|
||||
}
|
||||
|
||||
export default function DefaultAuthLayout(props: DefaultAuthLayoutProps) {
|
||||
const { children, illustrationBackground } = props;
|
||||
return (
|
||||
<Flex position="relative" h="max-content">
|
||||
<Flex
|
||||
minH="100vh"
|
||||
w="100%"
|
||||
maxW={{ base: '90%', md: '66%', lg: '1313px' }}
|
||||
mx="auto"
|
||||
pt={{ sm: '0px', md: '0px' }}
|
||||
px={{ lg: '30px', xl: '0px' }}
|
||||
ps={{ xl: '70px' }}
|
||||
justifyContent="start"
|
||||
direction="column"
|
||||
>
|
||||
<Link
|
||||
href="/"
|
||||
width={'fit-content'}
|
||||
mt={10}
|
||||
mb={{ base: '', md: '', lg: '120px', xl: '150px' }}
|
||||
>
|
||||
<Flex
|
||||
align="center"
|
||||
ps={{ base: '25px', lg: '0px' }}
|
||||
pt={{ lg: '0px', xl: '0px' }}
|
||||
w="fit-content"
|
||||
>
|
||||
<Icon
|
||||
as={FaChevronLeft}
|
||||
me="12px"
|
||||
h="13px"
|
||||
w="8px"
|
||||
color="gray.500"
|
||||
/>
|
||||
<Text ms="0px" fontSize="sm" color="gray.500">
|
||||
Back to the website
|
||||
</Text>
|
||||
</Flex>
|
||||
</Link>
|
||||
{children}
|
||||
<Box
|
||||
display={{ base: 'none', md: 'block' }}
|
||||
h="100%"
|
||||
minH="100vh"
|
||||
w={{ lg: '50vw', '2xl': '44vw' }}
|
||||
position="absolute"
|
||||
right="0px"
|
||||
>
|
||||
<Link href="/">
|
||||
<Flex
|
||||
bg={`url(${illustrationBackground})`}
|
||||
justify="center"
|
||||
align="end"
|
||||
w="100%"
|
||||
h="100%"
|
||||
bgSize="cover"
|
||||
bgPosition="50%"
|
||||
position="absolute"
|
||||
/>
|
||||
</Link>
|
||||
</Box>
|
||||
<Footer />
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
11
boilerplate-chakra-pro-main/components/card/Card.tsx
Normal file
11
boilerplate-chakra-pro-main/components/card/Card.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
'use client';
|
||||
import { useStyleConfig, chakra, forwardRef } from '@chakra-ui/react';
|
||||
import { CustomCardProps } from '@/theme/theme';
|
||||
const CustomCard = forwardRef<CustomCardProps, 'div'>((props, ref) => {
|
||||
const { size, variant, ...rest } = props;
|
||||
const styles = useStyleConfig('Card', { size, variant });
|
||||
|
||||
return <chakra.div ref={ref} __css={styles} {...rest} />;
|
||||
});
|
||||
|
||||
export default CustomCard;
|
||||
@@ -0,0 +1,64 @@
|
||||
'use client';
|
||||
|
||||
import Card from '@/components/card/Card';
|
||||
import {
|
||||
Flex,
|
||||
Stat,
|
||||
StatLabel,
|
||||
StatNumber,
|
||||
useColorModeValue,
|
||||
Text,
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
export default function Default(props: {
|
||||
startContent?: JSX.Element;
|
||||
endContent?: JSX.Element;
|
||||
name: string;
|
||||
growth?: string | number;
|
||||
value: string | number;
|
||||
}) {
|
||||
const { startContent, endContent, name, growth, value } = props;
|
||||
const textColor = useColorModeValue('#120F43', 'white');
|
||||
const textColorSecondary = 'gray.500';
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Flex
|
||||
my="auto"
|
||||
h="100%"
|
||||
align={{ base: 'center', xl: 'start' }}
|
||||
justify={{ base: 'center', xl: 'center' }}
|
||||
alignItems="center"
|
||||
>
|
||||
{startContent}
|
||||
|
||||
<Stat my="auto" ms={startContent ? '18px' : '0px'}>
|
||||
<StatLabel
|
||||
lineHeight="100%"
|
||||
color={textColorSecondary}
|
||||
fontSize="sm"
|
||||
mb="4px"
|
||||
>
|
||||
{name}
|
||||
</StatLabel>
|
||||
<StatNumber color={textColor} fontWeight="700" fontSize="lg">
|
||||
{value}
|
||||
</StatNumber>
|
||||
{growth ? (
|
||||
<Flex align="center">
|
||||
<Text color="green.500" fontSize="xs" fontWeight="700" me="5px">
|
||||
{growth}
|
||||
</Text>
|
||||
<Text color="gray.500" fontSize="xs" fontWeight="400">
|
||||
since last month
|
||||
</Text>
|
||||
</Flex>
|
||||
) : null}
|
||||
</Stat>
|
||||
<Flex ms="auto" w="max-content">
|
||||
{endContent}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
71
boilerplate-chakra-pro-main/components/card/TemplateCard.tsx
Normal file
71
boilerplate-chakra-pro-main/components/card/TemplateCard.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
'use client';
|
||||
|
||||
import NavLink from '../link/NavLink';
|
||||
import Card from '@/components/card/Card';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
useColorModeValue,
|
||||
Text,
|
||||
Icon,
|
||||
} from '@chakra-ui/react';
|
||||
import { MdEdit } from 'react-icons/md';
|
||||
|
||||
export default function Default(props: {
|
||||
illustration: string | JSX.Element;
|
||||
name: string;
|
||||
description: string;
|
||||
link: string;
|
||||
edit?: string;
|
||||
action?: any;
|
||||
admin?: boolean;
|
||||
}) {
|
||||
const { illustration, name, description, link, edit, admin } = props;
|
||||
const textColor = useColorModeValue('#120F43', 'white');
|
||||
const gray = useColorModeValue('gray.500', 'white');
|
||||
|
||||
return (
|
||||
<NavLink href={link}>
|
||||
<Card h="100%" py="24px" px="24px">
|
||||
<Flex
|
||||
my="auto"
|
||||
h="100%"
|
||||
direction={'column'}
|
||||
align={{ base: 'center', xl: 'start' }}
|
||||
justify={{ base: 'center', xl: 'center' }}
|
||||
>
|
||||
<Flex align="start" w="100%" mb="30px">
|
||||
<Text fontSize="34px" lineHeight={'120%'}>
|
||||
{illustration}
|
||||
</Text>
|
||||
{admin ? (
|
||||
<Flex ms="auto">
|
||||
<NavLink href={edit ? edit : '/admin/edit-template'}>
|
||||
<Button
|
||||
w="24px"
|
||||
h="24px"
|
||||
_hover={{}}
|
||||
_focus={{}}
|
||||
_active={{}}
|
||||
bg="none"
|
||||
>
|
||||
<Icon w="24px" h="24px" as={MdEdit} color={gray} />
|
||||
</Button>
|
||||
</NavLink>
|
||||
</Flex>
|
||||
) : null}
|
||||
</Flex>
|
||||
<Box>
|
||||
<Text fontSize="lg" color={textColor} fontWeight="700" mb="8px">
|
||||
{name}
|
||||
</Text>
|
||||
<Text fontSize="sm" color={gray} fontWeight="500">
|
||||
{description}
|
||||
</Text>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Card>
|
||||
</NavLink>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
'use client';
|
||||
import dynamic from 'next/dynamic';
|
||||
// import Chart from 'react-apexcharts';
|
||||
const Chart = dynamic(() => import('react-apexcharts'), {
|
||||
ssr: false
|
||||
});
|
||||
|
||||
const BarChart = (props) => {
|
||||
const { chartData, chartOptions } = props;
|
||||
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Chart
|
||||
options={chartOptions}
|
||||
type="bar"
|
||||
width="100%"
|
||||
height="100%"
|
||||
series={chartData}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BarChart;
|
||||
@@ -0,0 +1,23 @@
|
||||
'use client';
|
||||
import dynamic from 'next/dynamic';
|
||||
// import Chart from 'react-apexcharts';
|
||||
const Chart = dynamic(() => import('react-apexcharts'), {
|
||||
ssr: false
|
||||
});
|
||||
|
||||
const LineChart = (props) => {
|
||||
const { chartData, chartOptions } = props;
|
||||
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Chart
|
||||
options={chartOptions}
|
||||
type="line"
|
||||
width="100%"
|
||||
height="100%"
|
||||
series={chartData}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default LineChart;
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
'use client';
|
||||
|
||||
/*eslint-disable*/
|
||||
import Link from '@/components/link/Link';
|
||||
import {
|
||||
Flex,
|
||||
List,
|
||||
ListItem,
|
||||
Text,
|
||||
useColorModeValue,
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
export default function Footer() {
|
||||
const textColor = useColorModeValue('gray.500', 'white');
|
||||
return (
|
||||
<Flex
|
||||
zIndex="3"
|
||||
flexDirection={{
|
||||
base: 'column',
|
||||
xl: 'row',
|
||||
}}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
px={{ base: '30px', md: '50px' }}
|
||||
pb="30px"
|
||||
>
|
||||
<Text
|
||||
color={textColor}
|
||||
fontSize={{ base: 'xs', md: 'sm' }}
|
||||
textAlign={{
|
||||
base: 'center',
|
||||
xl: 'start',
|
||||
}}
|
||||
fontWeight="500"
|
||||
mb={{ base: '10px', xl: '0px' }}
|
||||
>
|
||||
{' '}
|
||||
© {new Date().getFullYear()}
|
||||
<Text as="span" fontWeight="500" ms="4px">
|
||||
Horizon UI Boilerplate. All Rights Reserved.
|
||||
</Text>
|
||||
</Text>
|
||||
<List display="flex">
|
||||
<ListItem
|
||||
me={{
|
||||
base: '10px',
|
||||
md: '44px',
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
fontWeight="500"
|
||||
fontSize={{ base: 'xs', md: 'sm' }}
|
||||
color={textColor}
|
||||
href="https://horizon-ui.com"
|
||||
>
|
||||
Homepage
|
||||
</Link>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
me={{
|
||||
base: '10px',
|
||||
md: '44px',
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
fontWeight="500"
|
||||
fontSize={{ base: 'xs', md: 'sm' }}
|
||||
color={textColor}
|
||||
href="https://horizon-ui.notion.site/Terms-Conditions-6e79229d25ed48f48a481962bc6de3ee"
|
||||
>
|
||||
Terms of Use
|
||||
</Link>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Link
|
||||
fontWeight="500"
|
||||
fontSize={{ base: 'xs', md: 'sm' }}
|
||||
color={textColor}
|
||||
href="https://horizon-ui.notion.site/Privacy-Policy-8addde50aa8e408ca5c5f5811c38f971"
|
||||
>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
'use client';
|
||||
|
||||
/*eslint-disable*/
|
||||
import {
|
||||
Flex,
|
||||
Link,
|
||||
List,
|
||||
ListItem,
|
||||
useColorModeValue
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
export default function Footer() {
|
||||
let textColor = useColorModeValue('gray.500', 'white');
|
||||
return (
|
||||
<Flex
|
||||
mt="auto"
|
||||
zIndex="3"
|
||||
flexDirection={{
|
||||
base: 'column',
|
||||
lg: 'row'
|
||||
}}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
px={{ base: '30px', md: '0px' }}
|
||||
pb="30px"
|
||||
>
|
||||
<List
|
||||
display="flex"
|
||||
flexDirection={{
|
||||
base: 'row',
|
||||
md: 'row'
|
||||
}}
|
||||
>
|
||||
<ListItem
|
||||
me={{
|
||||
base: '16px',
|
||||
md: '44px'
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
fontWeight="500"
|
||||
fontSize={{ base: '10px', md: 'sm' }}
|
||||
color={textColor}
|
||||
isExternal
|
||||
href="https://horizon-ui.notion.site/Terms-Conditions-6e79229d25ed48f48a481962bc6de3ee"
|
||||
>
|
||||
Terms & Conditions
|
||||
</Link>
|
||||
</ListItem>
|
||||
<ListItem
|
||||
me={{
|
||||
base: '16px',
|
||||
md: '44px'
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
fontWeight="500"
|
||||
fontSize={{ base: '10px', md: 'sm' }}
|
||||
color={textColor}
|
||||
isExternal
|
||||
href="https://horizon-ui.notion.site/Privacy-Policy-8addde50aa8e408ca5c5f5811c38f971"
|
||||
>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
<Link
|
||||
fontWeight="500"
|
||||
fontSize={{ base: '10px', md: 'sm' }}
|
||||
color={textColor}
|
||||
isExternal
|
||||
href="https://horizon-ui.notion.site/Refund-Policy-5d5fa25f7fac4978a0be6fcf3036c6ee"
|
||||
>
|
||||
Refund Policy
|
||||
</Link>
|
||||
</ListItem>
|
||||
</List>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
158
boilerplate-chakra-pro-main/components/footer/FooterWebsite.tsx
Normal file
158
boilerplate-chakra-pro-main/components/footer/FooterWebsite.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
/*eslint-disable*/
|
||||
'use client';
|
||||
|
||||
import logo from '/public/logo-horizon-boilerplate.png';
|
||||
import { HSeparator } from '@/components/separator/Separator';
|
||||
import { Flex, Link, Text, Image, useColorModeValue } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
export function FooterWebsite() {
|
||||
const textColorSecondary = useColorModeValue('gray.600', 'white');
|
||||
return (
|
||||
<Flex
|
||||
zIndex="3"
|
||||
flexDirection={{
|
||||
base: 'column',
|
||||
}}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
position="relative"
|
||||
px={{ base: '20px', xl: '0px' }}
|
||||
pb="50px"
|
||||
bg="white"
|
||||
>
|
||||
<HSeparator
|
||||
mb="0px"
|
||||
mt={{ base: '0px', md: '100px', lg: '0px' }}
|
||||
maxW="1170px"
|
||||
mx="auto"
|
||||
bg="gray.200"
|
||||
/>
|
||||
<Flex
|
||||
justifyContent="space-between"
|
||||
mt="50px"
|
||||
w={{ base: '100%', xl: '1170px' }}
|
||||
flexDirection={{
|
||||
base: 'column',
|
||||
lg: 'row',
|
||||
}}
|
||||
maxW={{ base: '100%', xl: '1170px' }}
|
||||
maxH="max-content"
|
||||
mx="auto"
|
||||
>
|
||||
<Link
|
||||
display={'flex'}
|
||||
alignItems="center"
|
||||
justifyContent={'center'}
|
||||
href="/"
|
||||
>
|
||||
<Image
|
||||
alt=" "
|
||||
w="36px"
|
||||
src={logo.src}
|
||||
/>
|
||||
<Text ms="8px" me="10px" fontWeight="800" color="#120F43">
|
||||
Horizon UI Boilerplate
|
||||
</Text>
|
||||
</Link>
|
||||
<Flex
|
||||
direction={{ base: 'column', md: 'row' }}
|
||||
justifyItems="center"
|
||||
alignItems="center"
|
||||
textAlign={'center'}
|
||||
w={{ base: '100%', md: '100%', lg: '100%', xl: 'unset' }}
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
justify={'center'}
|
||||
mx="auto"
|
||||
align="center"
|
||||
mb={{ base: '20px', lg: '0px' }}
|
||||
>
|
||||
<Flex my="auto" direction={{ base: 'column', md: 'row' }}>
|
||||
<Link
|
||||
isExternal={true}
|
||||
href="/pricing"
|
||||
fontSize="sm"
|
||||
color={textColorSecondary}
|
||||
fontWeight="500"
|
||||
letterSpacing="0px"
|
||||
me={{ base: '0px', md: '40px' }}
|
||||
mb={{ base: '20px', md: '0px' }}
|
||||
>
|
||||
Pricing
|
||||
</Link>
|
||||
<Link
|
||||
isExternal={true}
|
||||
href="/dashboard/settings"
|
||||
fontSize="sm"
|
||||
color={textColorSecondary}
|
||||
fontWeight="500"
|
||||
letterSpacing="0px"
|
||||
me={{ base: '0px', md: '40px' }}
|
||||
mb={{ base: '20px', md: '0px' }}
|
||||
>
|
||||
Account
|
||||
</Link>
|
||||
<Link
|
||||
isExternal={true}
|
||||
href="https://horizon-ui.notion.site/Refund-Policy-5d5fa25f7fac4978a0be6fcf3036c6ee"
|
||||
fontSize="sm"
|
||||
color={textColorSecondary}
|
||||
fontWeight="500"
|
||||
letterSpacing="0px"
|
||||
me={{ base: '0px', md: '40px' }}
|
||||
mb={{ base: '20px', md: '0px' }}
|
||||
>
|
||||
Refund Policy
|
||||
</Link>
|
||||
<Link
|
||||
isExternal={true}
|
||||
href="https://horizon-ui.notion.site/Privacy-Policy-8addde50aa8e408ca5c5f5811c38f971"
|
||||
fontSize="sm"
|
||||
color={textColorSecondary}
|
||||
fontWeight="500"
|
||||
letterSpacing="0px"
|
||||
me={{ base: '0px', md: '40px' }}
|
||||
mb={{ base: '20px', md: '0px' }}
|
||||
>
|
||||
Privacy Policy
|
||||
</Link>
|
||||
<Link
|
||||
isExternal={true}
|
||||
href="https://horizon-ui.notion.site/Terms-Conditions-6e79229d25ed48f48a481962bc6de3ee"
|
||||
fontSize="sm"
|
||||
color={textColorSecondary}
|
||||
fontWeight="500"
|
||||
letterSpacing="0px"
|
||||
me={{ base: '0px', md: '40px' }}
|
||||
mb={{ base: '20px', md: '0px' }}
|
||||
>
|
||||
Terms of service
|
||||
</Link>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex w="100%" maxW="1170px" px={{ base: '10px', md: '100px' }}>
|
||||
<Text
|
||||
lineHeight="180%"
|
||||
fontSize="sm"
|
||||
textAlign="center"
|
||||
color="gray.600"
|
||||
fontWeight="500"
|
||||
letterSpacing="0px"
|
||||
mt="40px"
|
||||
mb="40px"
|
||||
>
|
||||
<Text as="span" fontWeight={'700'}>
|
||||
Use it with caution:
|
||||
</Text>{' '}
|
||||
This tool can be helpful, but it is not a substitute for your own
|
||||
knowledge and understanding. Make sure to use it as a supplement to
|
||||
your own research and writing, rather than relying on it exclusively.
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
22
boilerplate-chakra-pro-main/components/icons/HeroBg.tsx
Normal file
22
boilerplate-chakra-pro-main/components/icons/HeroBg.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { createIcon } from '@chakra-ui/icons';
|
||||
|
||||
export const HeroBg = createIcon({
|
||||
displayName: 'HeroBg',
|
||||
viewBox: '0 0 1618 900',
|
||||
path: (
|
||||
<g
|
||||
width="1618"
|
||||
height="900"
|
||||
viewBox="0 0 1618 900"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M101.583 301.413C94.4517 259.209 122.883 219.215 165.087 212.083L1382.96 6.28832C1472.87 -8.9049 1512.05 115.615 1429.63 154.64L642.704 527.238L1520.96 365.296C1607.06 349.42 1650.61 465.12 1575.37 509.938L942.158 887.084C905.385 908.987 857.818 896.932 835.916 860.158C814.013 823.385 826.068 775.818 862.842 753.916L1129.5 595.09L95.0792 785.827C4.69665 802.492 -35.766 676.987 47.2513 637.679L863.283 251.3L190.913 364.917C148.709 372.048 108.715 343.617 101.583 301.413Z"
|
||||
fill="#E9E3FF"
|
||||
/>
|
||||
</g>
|
||||
)
|
||||
});
|
||||
42
boilerplate-chakra-pro-main/components/image/Avatar.tsx
Normal file
42
boilerplate-chakra-pro-main/components/image/Avatar.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
'use client';
|
||||
|
||||
import { Image } from './Image';
|
||||
import { chakra, useColorMode } from '@chakra-ui/system';
|
||||
import { ComponentProps } from 'react';
|
||||
|
||||
type AvatarImageProps = Partial<
|
||||
ComponentProps<typeof Image> & {
|
||||
showBorder?: boolean;
|
||||
}
|
||||
>;
|
||||
|
||||
export function NextAvatar({
|
||||
src,
|
||||
showBorder,
|
||||
alt = '',
|
||||
style,
|
||||
...props
|
||||
}: AvatarImageProps) {
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
return (
|
||||
<Image
|
||||
{...props}
|
||||
{...(showBorder
|
||||
? {
|
||||
border: '2px',
|
||||
borderColor: colorMode === 'dark' ? '#120F43' : 'white',
|
||||
}
|
||||
: {})}
|
||||
alt={alt}
|
||||
objectFit={'fill'}
|
||||
src={src}
|
||||
style={{ ...style, borderRadius: '50%' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export const ChakraNextAvatar = chakra(NextAvatar, {
|
||||
shouldForwardProp: (prop) =>
|
||||
['width', 'height', 'src', 'alt', 'layout'].includes(prop),
|
||||
});
|
||||
38
boilerplate-chakra-pro-main/components/image/Image.tsx
Normal file
38
boilerplate-chakra-pro-main/components/image/Image.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
'use client'
|
||||
import { Box, BoxProps } from '@chakra-ui/react';
|
||||
import NextImage, { ImageProps } from 'next/legacy/image';
|
||||
import { ComponentProps } from 'react';
|
||||
|
||||
type ChakraNextImageProps = Partial<ImageProps> &
|
||||
Partial<BoxProps> & {
|
||||
nextProps?: Partial<ComponentProps<typeof NextImage>>;
|
||||
};
|
||||
|
||||
function parseAssetPrefix(image: string) {
|
||||
const alreadyHasHttp = image.match('http');
|
||||
if (alreadyHasHttp) return image;
|
||||
|
||||
const prefix = process.env.NEXT_PUBLIC_BASE_PATH || '';
|
||||
const alreadyHasPrefix = image.match(prefix);
|
||||
|
||||
const finalUrl = alreadyHasPrefix ? image : `${prefix}${image}`;
|
||||
return finalUrl;
|
||||
}
|
||||
|
||||
export function Image(props: ChakraNextImageProps) {
|
||||
const { src, alt, nextProps = {}, ...rest } = props;
|
||||
|
||||
const imageUrl =
|
||||
typeof src === 'string' ? src : ((src as any)?.src as string);
|
||||
return (
|
||||
<Box overflow={'hidden'} position="relative" {...rest}>
|
||||
<NextImage
|
||||
layout="fill"
|
||||
objectFit="fill"
|
||||
src={parseAssetPrefix(imageUrl)}
|
||||
alt={alt}
|
||||
{...nextProps}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
371
boilerplate-chakra-pro-main/components/landing/faq.tsx
Normal file
371
boilerplate-chakra-pro-main/components/landing/faq.tsx
Normal file
@@ -0,0 +1,371 @@
|
||||
/*eslint-disable*/
|
||||
|
||||
import {
|
||||
Accordion,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
Flex,
|
||||
Text,
|
||||
Box,
|
||||
SimpleGrid,
|
||||
useColorModeValue,
|
||||
} from '@chakra-ui/react';
|
||||
import Link from 'next/link';
|
||||
|
||||
export default function Home() {
|
||||
const textColor = useColorModeValue('#120F43', 'white');
|
||||
return (
|
||||
<Box
|
||||
w="100%"
|
||||
pt={{ base: '90', md: '100px', xl: '140px' }}
|
||||
bg="linear-gradient(180deg, #F8FAFC 0%, rgba(255, 255, 255, 0) 47.33%)"
|
||||
id="faqs"
|
||||
bgSize="cover"
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
mx="auto"
|
||||
mb="40px"
|
||||
maxW={{ base: '90%', lg: '62%' }}
|
||||
justify="center"
|
||||
textAlign="center"
|
||||
>
|
||||
<Text
|
||||
as="h3"
|
||||
textAlign={{ base: 'center', lg: 'center' }}
|
||||
fontWeight="700"
|
||||
letterSpacing="2px"
|
||||
color="brand.500"
|
||||
fontSize={{ base: 'xs', md: 'md' }}
|
||||
w="100%"
|
||||
mb="10px"
|
||||
>
|
||||
FREQUENTLY ASKED QUESTIONS
|
||||
</Text>
|
||||
<Text
|
||||
as="h2"
|
||||
mx="auto"
|
||||
color={textColor}
|
||||
fontWeight="800"
|
||||
fontSize={{ base: '30px', md: '38px', lg: '38px', xl: '38px' }}
|
||||
lineHeight={{ base: '38px', md: '50px', lg: '50px', xl: '50px' }}
|
||||
mb={{ base: '10px', md: '20px' }}
|
||||
>
|
||||
Frequently asked questions
|
||||
</Text>
|
||||
<Text
|
||||
mx="auto"
|
||||
color="gray.600"
|
||||
fontSize={{ base: 'md', md: 'md', xl: 'lg' }}
|
||||
fontWeight="500"
|
||||
letterSpacing="0px"
|
||||
lineHeight={{ base: '24px', md: '30px' }}
|
||||
mb="30px"
|
||||
maxW={{ base: '100%', md: '80%', lg: '60%' }}
|
||||
>
|
||||
Looking for something else? Chat with us via{' '}
|
||||
<Link href="mailto:hello@horizon-ui.com ">hello@horizon-ui.com</Link>{' '}
|
||||
and we will try our best to help you with your questions!
|
||||
</Text>
|
||||
</Flex>
|
||||
<Box
|
||||
w="100%"
|
||||
maxW={{ base: '100%', md: '80%', lg: '860px' }}
|
||||
mx="auto"
|
||||
mb="120px"
|
||||
>
|
||||
<Accordion allowMultiple>
|
||||
<AccordionItem borderTop="0px solid">
|
||||
<Text>
|
||||
<AccordionButton
|
||||
py="25px"
|
||||
_hover={{ bg: 'none' }}
|
||||
fontSize="md"
|
||||
letterSpacing="0px"
|
||||
fontWeight={'700'}
|
||||
color={textColor}
|
||||
_active={{ boxShadow: 'none' }}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
>
|
||||
<Flex as="h3" flex={1} textAlign="left">
|
||||
What is Horizon UI Boilerplate?
|
||||
</Flex>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</Text>
|
||||
<AccordionPanel pb={4}>
|
||||
<SimpleGrid gap="40px" columns={1}>
|
||||
<Text
|
||||
color="gray.600"
|
||||
fontWeight={'500'}
|
||||
fontSize="md"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
This is an awesome example of how you can use our accordion
|
||||
component for your FAQs section to provide clear, concise
|
||||
answers while maintaining a clean and engaging user interface.
|
||||
The intuitive design allows users to easily navigate through
|
||||
common questions, expanding each section to find detailed
|
||||
information without overwhelming them with text. It's an ideal
|
||||
way to streamline your website's content and enhance user
|
||||
experience, ensuring that visitors have quick access to the
|
||||
answers they need.
|
||||
</Text>
|
||||
</SimpleGrid>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<Text>
|
||||
<AccordionButton
|
||||
py="25px"
|
||||
_hover={{ bg: 'none' }}
|
||||
fontSize="md"
|
||||
letterSpacing="0px"
|
||||
fontWeight={'700'}
|
||||
color={textColor}
|
||||
_active={{ boxShadow: 'none' }}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
>
|
||||
<Flex as="h3" flex={1} textAlign="left">
|
||||
Is Horizon UI Boilerplate Free?
|
||||
</Flex>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</Text>
|
||||
<AccordionPanel pb={4}>
|
||||
<SimpleGrid gap="40px" columns={1}>
|
||||
<Text
|
||||
color="gray.600"
|
||||
fontWeight={'500'}
|
||||
fontSize="md"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
This is an awesome example of how you can use our accordion
|
||||
component for your FAQs section to provide clear, concise
|
||||
answers while maintaining a clean and engaging user interface.
|
||||
The intuitive design allows users to easily navigate through
|
||||
common questions, expanding each section to find detailed
|
||||
information without overwhelming them with text. It's an ideal
|
||||
way to streamline your website's content and enhance user
|
||||
experience, ensuring that visitors have quick access to the
|
||||
answers they need.
|
||||
</Text>
|
||||
<Text
|
||||
color="gray.600"
|
||||
fontWeight={'500'}
|
||||
fontSize="md"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
You can learn more on our{' '}
|
||||
<Link href="/pricing">
|
||||
<Text as="span" fontWeight={'bold'} color="brand.500">
|
||||
Pricing Page.
|
||||
</Text>
|
||||
</Link>{' '}
|
||||
</Text>
|
||||
</SimpleGrid>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<Text>
|
||||
<AccordionButton
|
||||
py="25px"
|
||||
_hover={{ bg: 'none' }}
|
||||
fontSize="md"
|
||||
letterSpacing="0px"
|
||||
fontWeight={'700'}
|
||||
color={textColor}
|
||||
_active={{ boxShadow: 'none' }}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
>
|
||||
<Flex as="h3" flex={1} textAlign="left">
|
||||
How does Horizon UI Boilerplate work?
|
||||
</Flex>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</Text>
|
||||
<AccordionPanel pb={4}>
|
||||
<SimpleGrid gap="40px" columns={1}>
|
||||
<Text
|
||||
color="gray.600"
|
||||
fontWeight={'500'}
|
||||
fontSize="md"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
This is an awesome example of how you can use our accordion
|
||||
component for your FAQs section to provide clear, concise
|
||||
answers while maintaining a clean and engaging user interface.
|
||||
The intuitive design allows users to easily navigate through
|
||||
common questions, expanding each section to find detailed
|
||||
information without overwhelming them with text. It's an ideal
|
||||
way to streamline your website's content and enhance user
|
||||
experience, ensuring that visitors have quick access to the
|
||||
answers they need.
|
||||
</Text>
|
||||
</SimpleGrid>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<Text>
|
||||
<AccordionButton
|
||||
py="25px"
|
||||
_hover={{ bg: 'none' }}
|
||||
fontSize="md"
|
||||
letterSpacing="0px"
|
||||
fontWeight={'700'}
|
||||
color={textColor}
|
||||
_active={{ boxShadow: 'none' }}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
>
|
||||
<Flex as="h3" flex={1} textAlign="left">
|
||||
How can I use Horizon UI Boilerplate?
|
||||
</Flex>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</Text>
|
||||
<AccordionPanel pb={4}>
|
||||
<SimpleGrid gap="40px" columns={1}>
|
||||
<Text
|
||||
color="gray.600"
|
||||
fontWeight={'500'}
|
||||
fontSize="md"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
This is an awesome example of how you can use our accordion
|
||||
component for your FAQs section to provide clear, concise
|
||||
answers while maintaining a clean and engaging user interface.
|
||||
The intuitive design allows users to easily navigate through
|
||||
common questions, expanding each section to find detailed
|
||||
information without overwhelming them with text. It's an ideal
|
||||
way to streamline your website's content and enhance user
|
||||
experience, ensuring that visitors have quick access to the
|
||||
answers they need.
|
||||
</Text>
|
||||
</SimpleGrid>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<Text>
|
||||
<AccordionButton
|
||||
py="25px"
|
||||
_hover={{ bg: 'none' }}
|
||||
fontSize="md"
|
||||
letterSpacing="0px"
|
||||
fontWeight={'700'}
|
||||
color={textColor}
|
||||
_active={{ boxShadow: 'none' }}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
>
|
||||
<Flex as="h3" flex={1} textAlign="left">
|
||||
Is Horizon UI Boilerplate suitable for all academic levels?
|
||||
</Flex>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</Text>
|
||||
<AccordionPanel pb={4}>
|
||||
<SimpleGrid gap="40px" columns={1}>
|
||||
<Text
|
||||
color="gray.600"
|
||||
fontWeight={'500'}
|
||||
fontSize="md"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
This is an awesome example of how you can use our accordion
|
||||
component for your FAQs section to provide clear, concise
|
||||
answers while maintaining a clean and engaging user interface.
|
||||
The intuitive design allows users to easily navigate through
|
||||
common questions, expanding each section to find detailed
|
||||
information without overwhelming them with text. It's an ideal
|
||||
way to streamline your website's content and enhance user
|
||||
experience, ensuring that visitors have quick access to the
|
||||
answers they need.
|
||||
</Text>
|
||||
</SimpleGrid>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<Text>
|
||||
<AccordionButton
|
||||
py="25px"
|
||||
_hover={{ bg: 'none' }}
|
||||
fontSize="md"
|
||||
letterSpacing="0px"
|
||||
fontWeight={'700'}
|
||||
color={textColor}
|
||||
_active={{ boxShadow: 'none' }}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
>
|
||||
<Flex as="h3" flex={1} textAlign="left">
|
||||
Can I trust the quality of the essays generated by Essay
|
||||
Builder AI?
|
||||
</Flex>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</Text>
|
||||
<AccordionPanel pb={4}>
|
||||
<SimpleGrid gap="40px" columns={1}>
|
||||
<Text
|
||||
color="gray.600"
|
||||
fontWeight={'500'}
|
||||
fontSize="md"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
This is an awesome example of how you can use our accordion
|
||||
component for your FAQs section to provide clear, concise
|
||||
answers while maintaining a clean and engaging user interface.
|
||||
The intuitive design allows users to easily navigate through
|
||||
common questions, expanding each section to find detailed
|
||||
information without overwhelming them with text. It's an ideal
|
||||
way to streamline your website's content and enhance user
|
||||
experience, ensuring that visitors have quick access to the
|
||||
answers they need.
|
||||
</Text>
|
||||
</SimpleGrid>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
<AccordionItem>
|
||||
<Text>
|
||||
<AccordionButton
|
||||
py="25px"
|
||||
_hover={{ bg: 'none' }}
|
||||
fontSize="md"
|
||||
letterSpacing="0px"
|
||||
fontWeight={'700'}
|
||||
color={textColor}
|
||||
_active={{ boxShadow: 'none' }}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
>
|
||||
<Flex as="h3" flex={1} textAlign="left">
|
||||
Is the content generated by Horizon UI Boilerplate plagiarism-free?
|
||||
</Flex>
|
||||
<AccordionIcon />
|
||||
</AccordionButton>
|
||||
</Text>
|
||||
<AccordionPanel pb={4}>
|
||||
<SimpleGrid gap="40px" columns={1}>
|
||||
<Text
|
||||
color="gray.600"
|
||||
fontWeight={'500'}
|
||||
fontSize="md"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
This is an awesome example of how you can use our accordion
|
||||
component for your FAQs section to provide clear, concise
|
||||
answers while maintaining a clean and engaging user interface.
|
||||
The intuitive design allows users to easily navigate through
|
||||
common questions, expanding each section to find detailed
|
||||
information without overwhelming them with text. It's an ideal
|
||||
way to streamline your website's content and enhance user
|
||||
experience, ensuring that visitors have quick access to the
|
||||
answers they need.
|
||||
</Text>
|
||||
</SimpleGrid>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
133
boilerplate-chakra-pro-main/components/landing/feature-one.tsx
Normal file
133
boilerplate-chakra-pro-main/components/landing/feature-one.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import InnerContent from '@/components/layout/innerContent';
|
||||
import image from '@/public/img/features/feature-one.png';
|
||||
import {
|
||||
Flex,
|
||||
Link,
|
||||
Button,
|
||||
Icon,
|
||||
Image,
|
||||
Text,
|
||||
useColorModeValue,
|
||||
} from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import { MdChevronRight } from 'react-icons/md';
|
||||
|
||||
export default function FeatureOne() {
|
||||
const textColor = useColorModeValue('#120F43', 'white');
|
||||
const textColorSecondary = useColorModeValue('gray.600', 'white');
|
||||
return (
|
||||
<Flex
|
||||
w="100%"
|
||||
direction={{ base: 'column' }}
|
||||
pt={{ base: '100px', md: '140px', lg: '140px' }}
|
||||
pb={{ base: '100px', md: '140px', lg: '140px' }}
|
||||
overflow="hidden"
|
||||
bgSize="cover"
|
||||
position="relative"
|
||||
>
|
||||
<InnerContent px={{ base: '20px', md: '40px', xl: '0px' }}>
|
||||
<Flex
|
||||
align={'center'}
|
||||
direction={{ base: 'column', lg: 'row' }}
|
||||
maxW="100%"
|
||||
justifyContent="space-between"
|
||||
columnGap="50px"
|
||||
alignItems={{ base: 'center', lg: 'unset' }}
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
my="auto"
|
||||
maxW="100%"
|
||||
alignItems={{ base: 'center', lg: 'unset' }}
|
||||
>
|
||||
<Text
|
||||
as="h3"
|
||||
fontWeight="700"
|
||||
letterSpacing="2px"
|
||||
bg="brand.500"
|
||||
bgClip="text"
|
||||
fontSize={{ base: 'xs', md: 'md' }}
|
||||
textAlign={{ base: 'center', lg: 'left' }}
|
||||
w="100%"
|
||||
mb="10px"
|
||||
>
|
||||
YOUR STARTUP FEATURES
|
||||
</Text>
|
||||
<Text
|
||||
as="h2"
|
||||
fontWeight="800"
|
||||
color={textColor}
|
||||
fontSize={{ base: '30px', md: '40px', xl: '42px' }}
|
||||
lineHeight={{ base: '40px', md: '50px', lg: '52px' }}
|
||||
mb="20px"
|
||||
w={{ base: '100%', md: '80%', lg: '100%' }}
|
||||
textAlign={{ base: 'center', lg: 'left' }}
|
||||
maxW={{ base: '100%', md: 'unset' }}
|
||||
>
|
||||
Ready to use Web App
|
||||
<br />
|
||||
for your Startup project
|
||||
</Text>
|
||||
<Text
|
||||
color={textColorSecondary}
|
||||
textAlign={{ base: 'center', lg: 'left' }}
|
||||
fontSize={{ base: 'sm', md: 'md', xl: 'md' }}
|
||||
w={{ base: '94%', md: '94%', lg: '97%' }}
|
||||
lineHeight={{ base: '24px', md: '30px' }}
|
||||
fontWeight="500"
|
||||
letterSpacing="0px"
|
||||
mb="30px"
|
||||
>
|
||||
It’s so easy to beat your endless procrastination when you have
|
||||
all the necessary resources to get that project done and start to
|
||||
generate your startup’s first dollar in just a few days.
|
||||
</Text>
|
||||
<Flex
|
||||
align="center"
|
||||
direction={{ base: 'column', md: 'row' }}
|
||||
mb={{ md: '0px', lg: '30px' }}
|
||||
justifyContent={{ base: 'center', lg: 'unset' }}
|
||||
>
|
||||
<Link href="/dashboard/signin" me={{ base: '0px', md: '14px' }}>
|
||||
<Button
|
||||
py="20px"
|
||||
px="16px"
|
||||
fontSize="sm"
|
||||
variant="primary"
|
||||
borderRadius="45px"
|
||||
mb={{ base: '20px', md: '0px' }}
|
||||
w={{ base: '335px', md: '230px' }}
|
||||
h="54px"
|
||||
>
|
||||
Get started now
|
||||
<Icon as={MdChevronRight} mt="2px" h="16px" w="16px" />
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/dashboard/signin">
|
||||
<Button
|
||||
py="20px"
|
||||
px="16px"
|
||||
fontSize="sm"
|
||||
variant="setup"
|
||||
borderRadius="45px"
|
||||
mb={{ base: '20px', md: '0px' }}
|
||||
w={{ base: '335px', md: '160px' }}
|
||||
h="54px"
|
||||
>
|
||||
Try it for Free
|
||||
<Icon as={MdChevronRight} mt="2px" h="16px" w="16px" />
|
||||
</Button>
|
||||
</Link>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Image
|
||||
alt=" "
|
||||
src={image.src}
|
||||
w={{ base: '100%', lg: '415px', xl: '575px' }}
|
||||
mt={{ base: '20px', md: '50px', lg: '0px' }}
|
||||
/>
|
||||
</Flex>
|
||||
</InnerContent>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
130
boilerplate-chakra-pro-main/components/landing/feature-three.tsx
Normal file
130
boilerplate-chakra-pro-main/components/landing/feature-three.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
import InnerContent from '@/components/layout/innerContent';
|
||||
import image from '@/public/img/features/feature-three.png';
|
||||
import {
|
||||
Flex,
|
||||
Link,
|
||||
Button,
|
||||
Icon,
|
||||
Image,
|
||||
Text,
|
||||
useColorModeValue,
|
||||
} from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import { MdChevronRight } from 'react-icons/md';
|
||||
|
||||
export default function FeatureOne() {
|
||||
const textColor = useColorModeValue('#120F43', 'white');
|
||||
const textColorSecondary = useColorModeValue('gray.600', 'white');
|
||||
return (
|
||||
<Flex
|
||||
w="100%"
|
||||
direction={{ base: 'column' }}
|
||||
pb={{ base: '100px', md: '140px', lg: '140px' }}
|
||||
overflow="hidden"
|
||||
bgSize="cover"
|
||||
position="relative"
|
||||
>
|
||||
<InnerContent px={{ base: '20px', md: '40px', xl: '0px' }}>
|
||||
<Flex
|
||||
align={'center'}
|
||||
direction={{ base: 'column', lg: 'row' }}
|
||||
maxW="100%"
|
||||
justifyContent="space-between"
|
||||
columnGap="50px"
|
||||
alignItems={{ base: 'center', lg: 'unset' }}
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
my="auto"
|
||||
maxW="100%"
|
||||
alignItems={{ base: 'center', lg: 'unset' }}
|
||||
>
|
||||
<Text
|
||||
as="h3"
|
||||
fontWeight="700"
|
||||
letterSpacing="2px"
|
||||
bg="linear-gradient(89deg, #3D1DFF 9.03%, #6147FF 10.23%, #D451FF 20.91%, #EC458D 30.02%, #FFCA8B 44.68%)"
|
||||
bgClip="text"
|
||||
fontSize={{ base: 'xs', md: 'md' }}
|
||||
textAlign={{ base: 'center', lg: 'left' }}
|
||||
w="100%"
|
||||
mb="10px"
|
||||
>
|
||||
DISCOVER MORE WITH PRO
|
||||
</Text>
|
||||
<Text
|
||||
as="h2"
|
||||
fontWeight="800"
|
||||
color={textColor}
|
||||
fontSize={{ base: '26px', md: '40px', xl: '42px' }}
|
||||
lineHeight={{ base: '36px', md: '50px', lg: '52px' }}
|
||||
mb="20px"
|
||||
w={{ base: '100%', md: '80%', lg: '100%' }}
|
||||
textAlign={{ base: 'center', lg: 'left' }}
|
||||
maxW={{ base: '100%', md: 'unset' }}
|
||||
>
|
||||
Generate an Outstanding SaaS without limitations
|
||||
</Text>
|
||||
<Text
|
||||
color={textColorSecondary}
|
||||
textAlign={{ base: 'center', lg: 'left' }}
|
||||
fontSize={{ base: 'sm', md: 'md', xl: 'md' }}
|
||||
w={{ base: '100%', md: '80%', lg: '97%' }}
|
||||
lineHeight={{ base: '24px', md: '30px' }}
|
||||
fontWeight="500"
|
||||
letterSpacing="0px"
|
||||
mb="30px"
|
||||
>
|
||||
Give life to your startup project by choosing from a premium pack
|
||||
of top-notch landing sections like Hero, Features, Call to
|
||||
Actions, Pricing, Navigations, Auth pages, Dashboard, and so on.
|
||||
</Text>
|
||||
<Flex
|
||||
align="center"
|
||||
direction={{ base: 'column', md: 'row' }}
|
||||
mb={{ md: '0px', lg: '30px' }}
|
||||
justifyContent={{ base: 'center', lg: 'unset' }}
|
||||
>
|
||||
<Link href="/pricing" me={{ base: '0px', md: '14px' }}>
|
||||
<Button
|
||||
py="20px"
|
||||
px="16px"
|
||||
fontSize="sm"
|
||||
variant="primary"
|
||||
borderRadius="45px"
|
||||
mb={{ base: '20px', md: '0px' }}
|
||||
w={{ base: '335px', md: '210px' }}
|
||||
h="54px"
|
||||
>
|
||||
Get started with PRO
|
||||
<Icon as={MdChevronRight} mt="2px" h="16px" w="16px" />
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/pricing">
|
||||
<Button
|
||||
py="20px"
|
||||
px="16px"
|
||||
fontSize="sm"
|
||||
variant="setup"
|
||||
borderRadius="45px"
|
||||
mb={{ base: '20px', md: '0px' }}
|
||||
w={{ base: '335px', md: '190px' }}
|
||||
h="54px"
|
||||
>
|
||||
See pricing Plans
|
||||
<Icon as={MdChevronRight} mt="2px" h="16px" w="16px" />
|
||||
</Button>
|
||||
</Link>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Image
|
||||
alt=" "
|
||||
src={image.src}
|
||||
w={{ base: '100%', lg: '415px', xl: '575px' }}
|
||||
mt={{ base: '20px', md: '50px', lg: '0px' }}
|
||||
/>
|
||||
</Flex>
|
||||
</InnerContent>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
129
boilerplate-chakra-pro-main/components/landing/feature-two.tsx
Normal file
129
boilerplate-chakra-pro-main/components/landing/feature-two.tsx
Normal file
@@ -0,0 +1,129 @@
|
||||
import InnerContent from '@/components/layout/innerContent';
|
||||
import image from '@/public/img/features/feature-two.png';
|
||||
import {
|
||||
Flex,
|
||||
Link,
|
||||
Button,
|
||||
Icon,
|
||||
Image,
|
||||
Text,
|
||||
useColorModeValue,
|
||||
} from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import { MdChevronRight } from 'react-icons/md';
|
||||
|
||||
export default function FeatureOne() {
|
||||
const textColor = useColorModeValue('#120F43', 'white');
|
||||
const textColorSecondary = useColorModeValue('gray.600', 'white');
|
||||
return (
|
||||
<Flex
|
||||
w="100%"
|
||||
direction={{ base: 'column' }}
|
||||
pb={{ base: '100px', md: '140px', lg: '140px' }}
|
||||
overflow="hidden"
|
||||
bgSize="cover"
|
||||
position="relative"
|
||||
>
|
||||
<InnerContent px={{ base: '20px', md: '40px', xl: '0px' }}>
|
||||
<Flex
|
||||
align={'center'}
|
||||
direction={{ base: 'column-reverse', lg: 'row' }}
|
||||
maxW="100%"
|
||||
justifyContent="space-between"
|
||||
columnGap="90px"
|
||||
alignItems={{ base: 'center', lg: 'unset' }}
|
||||
>
|
||||
<Image
|
||||
alt=" "
|
||||
src={image.src}
|
||||
w={{ base: '100%', lg: '415px', xl: '575px' }}
|
||||
mt={{ base: '20px', md: '50px', lg: '0px' }}
|
||||
/>
|
||||
<Flex
|
||||
direction="column"
|
||||
my="auto"
|
||||
maxW="100%"
|
||||
alignItems={{ base: 'center', lg: 'unset' }}
|
||||
>
|
||||
<Text
|
||||
as="h3"
|
||||
fontWeight="700"
|
||||
letterSpacing="2px"
|
||||
bg="linear-gradient(89deg, #3D1DFF 9.03%, #6147FF 10.23%, #D451FF 20.91%, #EC458D 30.02%, #FFCA8B 44.68%)"
|
||||
bgClip="text"
|
||||
fontSize={{ base: 'xs', md: 'md' }}
|
||||
textAlign={{ base: 'center', lg: 'left' }}
|
||||
mb="10px"
|
||||
>
|
||||
EXCLUSIVE PRO FEATURE
|
||||
</Text>
|
||||
<Text
|
||||
as="h2"
|
||||
fontWeight="800"
|
||||
color={textColor}
|
||||
fontSize={{ base: '30px', md: '40px', xl: '42px' }}
|
||||
lineHeight={{ base: '40px', md: '50px', lg: '52px' }}
|
||||
mb="20px"
|
||||
w={{ base: '100%', md: '70%', lg: '100%' }}
|
||||
textAlign={{ base: 'center', lg: 'left' }}
|
||||
maxW={{ base: '100%', md: 'unset' }}
|
||||
>
|
||||
Advanced Platform for your Startup Web App
|
||||
</Text>
|
||||
<Text
|
||||
color={textColorSecondary}
|
||||
textAlign={{ base: 'center', lg: 'left' }}
|
||||
fontSize={{ base: 'sm', md: 'md', xl: 'md' }}
|
||||
w={{ base: '100%', md: '80%', lg: '100%' }}
|
||||
lineHeight={{ base: '24px', md: '30px' }}
|
||||
fontWeight="500"
|
||||
letterSpacing="0px"
|
||||
mb="30px"
|
||||
>
|
||||
Horizon UI Boilerplate comes with a premium pack that includes all
|
||||
the necessary resources to launch your startup, like the fully
|
||||
coded web app pages, landing, database, payments and so on.
|
||||
</Text>
|
||||
<Flex
|
||||
align="center"
|
||||
direction={{ base: 'column', md: 'row' }}
|
||||
mb={{ md: '0px', lg: '30px' }}
|
||||
justifyContent={{ base: 'center', lg: 'unset' }}
|
||||
>
|
||||
<Link href="/pricing" me={{ base: '0px', md: '14px' }}>
|
||||
<Button
|
||||
py="20px"
|
||||
px="16px"
|
||||
fontSize="sm"
|
||||
variant="primary"
|
||||
borderRadius="45px"
|
||||
mb={{ base: '20px', md: '0px' }}
|
||||
w={{ base: '335px', md: '210px' }}
|
||||
h="54px"
|
||||
>
|
||||
Get started now
|
||||
<Icon as={MdChevronRight} mt="2px" h="16px" w="16px" />
|
||||
</Button>
|
||||
</Link>
|
||||
<Link href="/pricing">
|
||||
<Button
|
||||
py="20px"
|
||||
px="16px"
|
||||
fontSize="sm"
|
||||
variant="setup"
|
||||
borderRadius="45px"
|
||||
mb={{ base: '20px', md: '0px' }}
|
||||
w={{ base: '335px', md: '190px' }}
|
||||
h="54px"
|
||||
>
|
||||
Try it for Free
|
||||
<Icon as={MdChevronRight} mt="2px" h="16px" w="16px" />
|
||||
</Button>
|
||||
</Link>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</InnerContent>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
294
boilerplate-chakra-pro-main/components/landing/first-section.tsx
Normal file
294
boilerplate-chakra-pro-main/components/landing/first-section.tsx
Normal file
@@ -0,0 +1,294 @@
|
||||
import { HeroBg } from '@/components/icons/HeroBg';
|
||||
import dashboard from '@/public/img/first-section/dashboard-world-greatest-section.png';
|
||||
import left from '@/public/img/first-section/left-image-worlds-greatest.png';
|
||||
import right from '@/public/img/first-section/right-image-worlds-greatest.png';
|
||||
import nextjs from '@/public/img/hero/nextjs.png';
|
||||
import openai from '@/public/img/hero/openai.png';
|
||||
import stripe from '@/public/img/hero/stripe.png';
|
||||
import supabase from '@/public/img/hero/supabase.png';
|
||||
import chakra from '@/public/img/hero/chakra.png';
|
||||
import userauth from '@/public/img/hero/user-auth.png';
|
||||
import {
|
||||
Box,
|
||||
Icon,
|
||||
Flex,
|
||||
Image,
|
||||
Text,
|
||||
useColorModeValue,
|
||||
SimpleGrid,
|
||||
} from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
export default function FirstSection() {
|
||||
const brandColorPrice = useColorModeValue('brand.500', 'white');
|
||||
const textColor = useColorModeValue('#120F43', 'white');
|
||||
return (
|
||||
<Flex
|
||||
zIndex="2"
|
||||
w="100%"
|
||||
direction={{ base: 'column' }}
|
||||
bgSize="cover"
|
||||
position="relative"
|
||||
pt={{ base: '90px', md: '100px', xl: '100px' }}
|
||||
>
|
||||
<SimpleGrid
|
||||
columns={{ base: 1, md: 3, xl: 6 }}
|
||||
w="100%"
|
||||
gap="16px"
|
||||
maxW="1170px"
|
||||
mx="auto"
|
||||
mb="86px"
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
align="center"
|
||||
justify="center"
|
||||
border="1px solid"
|
||||
borderColor="secondaryGray.400"
|
||||
borderRadius="14px"
|
||||
py="39px"
|
||||
>
|
||||
<Image
|
||||
borderRadius="12px"
|
||||
mb="20px"
|
||||
src={nextjs.src}
|
||||
w="60px"
|
||||
h="60px"
|
||||
alt="nextjs logo"
|
||||
/>
|
||||
<Text fontWeight="bold" color={textColor}>
|
||||
NextJS 14
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
direction="column"
|
||||
align="center"
|
||||
justify="center"
|
||||
border="1px solid"
|
||||
borderColor="secondaryGray.400"
|
||||
borderRadius="14px"
|
||||
py="39px"
|
||||
>
|
||||
<Image
|
||||
borderRadius="12px"
|
||||
mb="20px"
|
||||
src={stripe.src}
|
||||
w="60px"
|
||||
h="60px"
|
||||
alt="stripe logo"
|
||||
/>
|
||||
<Text fontWeight="bold" color={textColor}>
|
||||
Stripe
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
direction="column"
|
||||
align="center"
|
||||
justify="center"
|
||||
border="1px solid"
|
||||
borderColor="secondaryGray.400"
|
||||
borderRadius="14px"
|
||||
py="39px"
|
||||
>
|
||||
<Image
|
||||
borderRadius="12px"
|
||||
mb="20px"
|
||||
src={supabase.src}
|
||||
w="60px"
|
||||
h="60px"
|
||||
alt="supabase logo"
|
||||
/>
|
||||
<Text fontWeight="bold" color={textColor}>
|
||||
Supabase
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
direction="column"
|
||||
align="center"
|
||||
justify="center"
|
||||
border="1px solid"
|
||||
borderColor="secondaryGray.400"
|
||||
borderRadius="14px"
|
||||
py="39px"
|
||||
>
|
||||
<Image
|
||||
borderRadius="12px"
|
||||
mb="20px"
|
||||
src={chakra.src}
|
||||
w="60px"
|
||||
h="60px"
|
||||
alt="chakra ui logo"
|
||||
/>
|
||||
<Text fontWeight="bold" color={textColor}>
|
||||
Chakra UI
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
direction="column"
|
||||
align="center"
|
||||
justify="center"
|
||||
border="1px solid"
|
||||
borderColor="secondaryGray.400"
|
||||
borderRadius="14px"
|
||||
py="39px"
|
||||
>
|
||||
<Image
|
||||
borderRadius="12px"
|
||||
mb="20px"
|
||||
src={openai.src}
|
||||
w="60px"
|
||||
h="60px"
|
||||
alt="openai logo"
|
||||
/>
|
||||
<Text fontWeight="bold" color={textColor}>
|
||||
AI Integration
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
direction="column"
|
||||
align="center"
|
||||
justify="center"
|
||||
border="1px solid"
|
||||
borderColor="secondaryGray.400"
|
||||
borderRadius="14px"
|
||||
py="39px"
|
||||
>
|
||||
<Image
|
||||
borderRadius="12px"
|
||||
mb="20px"
|
||||
src={userauth.src}
|
||||
w="60px"
|
||||
h="60px"
|
||||
alt="auth0 logo"
|
||||
/>
|
||||
<Text fontWeight="bold" color={textColor}>
|
||||
User Auth
|
||||
</Text>
|
||||
</Flex>
|
||||
</SimpleGrid>
|
||||
<Flex
|
||||
direction="column"
|
||||
px={{ base: '20px', md: '40px', xl: '0px' }}
|
||||
maxW="unset"
|
||||
>
|
||||
<Flex direction="column" width="stretch">
|
||||
<Flex
|
||||
direction="column"
|
||||
mx="auto"
|
||||
alignItems="center"
|
||||
textAlign="center"
|
||||
>
|
||||
<Text
|
||||
as="h3"
|
||||
textAlign={{ base: 'center', lg: 'center' }}
|
||||
fontWeight="700"
|
||||
letterSpacing="2px"
|
||||
color={brandColorPrice}
|
||||
fontSize={{ base: 'xs', md: 'md' }}
|
||||
w="100%"
|
||||
mb="10px"
|
||||
>
|
||||
BEST AI NEXTJS BOILERPLATE
|
||||
</Text>
|
||||
<Text
|
||||
as="h2"
|
||||
textAlign={{ base: 'center' }}
|
||||
color={textColor}
|
||||
fontWeight="800"
|
||||
fontSize={{ base: '30px', md: '48px', lg: '48px', xl: '58px' }}
|
||||
lineHeight={{ base: '38px', md: '60px', lg: '60px', xl: '70px' }}
|
||||
mb={{ base: '20px', md: '30px' }}
|
||||
w={{ base: '100%', md: '80%', lg: '60%', xl: '50%' }}
|
||||
mx="auto"
|
||||
>
|
||||
Your All-in-One <br />
|
||||
Startup Boilerplate
|
||||
</Text>
|
||||
<Text
|
||||
color="gray.600"
|
||||
fontSize={{ base: 'sm', md: 'md', xl: 'lg' }}
|
||||
fontWeight="500"
|
||||
letterSpacing="0px"
|
||||
lineHeight={{ base: '24px', md: '30px' }}
|
||||
w={{
|
||||
base: '100%',
|
||||
md: '80%',
|
||||
lg: '80%',
|
||||
xl: '56%',
|
||||
'2xl': '54%',
|
||||
}}
|
||||
>
|
||||
Tap into the power of Artificial Intelligence for your startup
|
||||
needs with Horizon UI Boilerplate, the most complex NextJS
|
||||
boilerplate to launch your web app project in just a few moments.
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex
|
||||
position={'relative'}
|
||||
justifyContent="center"
|
||||
mt={{ base: '16px', md: '10px', lg: '40px' }}
|
||||
maxW={{ base: '335px', md: '1170px' }}
|
||||
mx="auto"
|
||||
>
|
||||
<Icon
|
||||
as={HeroBg}
|
||||
position="absolute"
|
||||
mx="auto"
|
||||
w={{
|
||||
base: '500px',
|
||||
md: '750px',
|
||||
lg: '1000px',
|
||||
xl: '1800px',
|
||||
}}
|
||||
h={{
|
||||
base: '500px',
|
||||
md: '750px',
|
||||
lg: '1000px',
|
||||
xl: '820px',
|
||||
}}
|
||||
left="50%"
|
||||
transform="translate(-50%,0px)"
|
||||
top={{
|
||||
base: '-130px',
|
||||
md: '-90px',
|
||||
lg: '-100px',
|
||||
xl: '-80px',
|
||||
}}
|
||||
/>
|
||||
<Image
|
||||
src={dashboard.src}
|
||||
zIndex={'1'}
|
||||
alt=""
|
||||
maxH="max-content"
|
||||
w="100%"
|
||||
maxW={{ base: '335px', md: '1170px' }}
|
||||
borderRadius="8px"
|
||||
boxShadow="0px 26.83487px 155.64224px -46.15597px #CBD5E0"
|
||||
/>
|
||||
<Image
|
||||
zIndex={'1'}
|
||||
src={left.src}
|
||||
alt=""
|
||||
position={'absolute'}
|
||||
w={{ base: '62px', md: '122px', xl: '224px' }}
|
||||
left={{ base: '1px', md: '5px', lg: '10px', xl: '-20px' }}
|
||||
top={{ base: '33px', md: '75px', lg: '36px', xl: '45px' }}
|
||||
boxShadow="0px 10.1683px 61.0098px rgba(0, 0, 0, 0.05)"
|
||||
borderRadius="8px"
|
||||
/>
|
||||
<Image
|
||||
src={right.src}
|
||||
alt=""
|
||||
position="absolute"
|
||||
right={{ base: '-16px', md: '-30px', lg: '-80px', xl: '-53px' }}
|
||||
top={{ base: '48px', md: '105px', lg: '44px', xl: '98px' }}
|
||||
w={{ base: '286px', md: '592px', lg: '806px', xl: '1026px' }}
|
||||
borderRadius="8px"
|
||||
zIndex={'1'}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
183
boilerplate-chakra-pro-main/components/landing/hero.tsx
Normal file
183
boilerplate-chakra-pro-main/components/landing/hero.tsx
Normal file
@@ -0,0 +1,183 @@
|
||||
import imageLeft from '@/public/img/hero/left-image.png';
|
||||
import imageRight from '@/public/img/hero/right-image.png';
|
||||
import { Button, Image, Icon, Flex, Link, Text } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import { IoIosStar } from 'react-icons/io';
|
||||
import { MdChevronRight } from 'react-icons/md';
|
||||
|
||||
export default function hero() {
|
||||
return (
|
||||
<Flex
|
||||
mx="auto"
|
||||
mt={{ base: '90px', lg: '114px' }}
|
||||
w="96vw"
|
||||
h={{
|
||||
base: '560px',
|
||||
md: '580px',
|
||||
lg: '580px',
|
||||
xl: '620px',
|
||||
'2xl': '84vh',
|
||||
}}
|
||||
overflow="hidden"
|
||||
alignItems="center"
|
||||
alignContent="center"
|
||||
position={'relative'}
|
||||
maxW="100%"
|
||||
direction={{ base: 'column' }}
|
||||
bg="var(--linear-3, radial-gradient(98.96% 75.83% at 50% 0%, #948FE8 0%, #363285 22.92%, #110D5B 42.71%, #050327 88.54%))"
|
||||
borderRadius={{ base: '20px', md: '30px' }}
|
||||
>
|
||||
<Flex h="100%" w="100%">
|
||||
<Flex
|
||||
maxW="100%"
|
||||
h="100%"
|
||||
direction="row"
|
||||
width="stretch"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
alignContent="center"
|
||||
mb={{ base: '0px', md: '30px' }}
|
||||
>
|
||||
<Image
|
||||
position="absolute"
|
||||
left="0"
|
||||
display={{ base: 'none', xl: 'unset' }}
|
||||
src={imageLeft.src}
|
||||
alt=" "
|
||||
w={{ base: '90%', md: '100%', lg: '30%', xl: '30%' }}
|
||||
/>
|
||||
<Flex direction="column" mx="auto" textAlign="center">
|
||||
<Text
|
||||
as="h1"
|
||||
color="white"
|
||||
zIndex="100"
|
||||
fontSize={{
|
||||
base: '32px',
|
||||
md: '44px',
|
||||
lg: '44px',
|
||||
xl: '44px',
|
||||
'2xl': '58px',
|
||||
}}
|
||||
lineHeight={{
|
||||
base: '40px',
|
||||
md: '54px',
|
||||
lg: '54px',
|
||||
xl: '54px',
|
||||
'2xl': '68px',
|
||||
}}
|
||||
w={{
|
||||
base: '90%',
|
||||
md: '80%',
|
||||
lg: '60%',
|
||||
xl: '50%',
|
||||
'2xl': '50%',
|
||||
'3xl': '50%',
|
||||
}}
|
||||
alignSelf="center"
|
||||
mb={{ base: '16px', md: '20px' }}
|
||||
fontWeight="700"
|
||||
>
|
||||
Launch your startup project 10X faster in a few moments
|
||||
</Text>
|
||||
<Text
|
||||
as="p"
|
||||
mb={{ base: '30px', md: '30px' }}
|
||||
color={'white'}
|
||||
alignSelf="center"
|
||||
fontSize={{ base: 'sm', md: 'md', '2xl': 'md' }}
|
||||
lineHeight={{ base: '24px', md: '30px' }}
|
||||
letterSpacing="0px"
|
||||
fontWeight="500"
|
||||
w={{
|
||||
base: '91%',
|
||||
md: '82%',
|
||||
lg: '62%',
|
||||
xl: '44%',
|
||||
'2xl': '41%',
|
||||
'3xl': '41%',
|
||||
}}
|
||||
>
|
||||
Create a professional website for your startup in no time with
|
||||
Horizon UI Boilerplate. Our comprehensive template will help you
|
||||
launch your project 10X faster, leaving you more time to focus on
|
||||
your business.
|
||||
</Text>
|
||||
<Link
|
||||
w={{ base: '300px', md: '340px' }}
|
||||
alignSelf="center"
|
||||
href="/dashboard/signin"
|
||||
>
|
||||
<Button
|
||||
variant="primary"
|
||||
py="24px"
|
||||
bg="linear-gradient(15.46deg, #4A25E1 26.3%, #7B5AFF 86.4%) !important"
|
||||
px="24px"
|
||||
fontSize="md"
|
||||
borderRadius="45px"
|
||||
w={{ base: '300px', md: '340px' }}
|
||||
h="70px"
|
||||
>
|
||||
Go to dashboard
|
||||
<Icon
|
||||
as={MdChevronRight}
|
||||
color="white"
|
||||
mt="4px"
|
||||
ms="2px"
|
||||
h="16px"
|
||||
w="16px"
|
||||
/>
|
||||
</Button>
|
||||
</Link>
|
||||
<Flex
|
||||
align="center"
|
||||
direction="column"
|
||||
justifyContent={{ base: 'center', lg: 'center' }}
|
||||
>
|
||||
{' '}
|
||||
<Text
|
||||
as="h3"
|
||||
mt={{ base: '20px', md: '20px' }}
|
||||
mb="10px"
|
||||
color="white"
|
||||
alignSelf="center"
|
||||
fontSize="md"
|
||||
letterSpacing="0px"
|
||||
fontWeight={'500'}
|
||||
>
|
||||
Used by 80,000+ users monthly
|
||||
</Text>
|
||||
<Flex
|
||||
justify={{ base: 'center', md: 'start' }}
|
||||
alignItems="center"
|
||||
>
|
||||
<Icon as={IoIosStar} w="22px" h="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} w="22px" h="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} w="22px" h="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} w="22px" h="22px" color="#F6AD55" />
|
||||
<Icon
|
||||
as={IoIosStar}
|
||||
w="22px"
|
||||
h="22px"
|
||||
color="orange.300"
|
||||
me="8px"
|
||||
/>
|
||||
<Text color="white" mt="2px" fontWeight="bold" fontSize="lg">
|
||||
4.9
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Image
|
||||
position="absolute"
|
||||
right="0"
|
||||
display={{ base: 'none', xl: 'unset' }}
|
||||
src={imageRight.src}
|
||||
alt=" "
|
||||
w={{ base: '90%', md: '100%', lg: '30%', xl: '30%' }}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
141
boilerplate-chakra-pro-main/components/landing/index.tsx
Normal file
141
boilerplate-chakra-pro-main/components/landing/index.tsx
Normal file
@@ -0,0 +1,141 @@
|
||||
/*eslint-disable*/
|
||||
'use client';
|
||||
|
||||
import { FooterWebsite } from '@/components/footer/FooterWebsite';
|
||||
import Faq from '@/components/landing/faq';
|
||||
import FeatureOne from '@/components/landing/feature-one';
|
||||
import FeatureThree from '@/components/landing/feature-three';
|
||||
import FeatureTwo from '@/components/landing/feature-two';
|
||||
import FirstSection from '@/components/landing/first-section';
|
||||
import Hero from '@/components/landing/hero';
|
||||
import SecondSection from '@/components/landing/second-section';
|
||||
import NavbarFixed from '@/components/navbar/NavbarFixed';
|
||||
import { Database } from '@/types_db';
|
||||
import { Button, Flex, Input, Text } from '@chakra-ui/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;
|
||||
}
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<NavbarFixed />
|
||||
<Flex
|
||||
direction="column"
|
||||
align="center"
|
||||
h="100%"
|
||||
minH="100vh"
|
||||
overflow="hidden"
|
||||
position="relative"
|
||||
>
|
||||
<Flex
|
||||
w="100%"
|
||||
direction="column"
|
||||
pb={{ base: '0px', md: '80px', lg: '80px', xl: '80px' }}
|
||||
position="relative"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Hero />
|
||||
<FirstSection />
|
||||
<SecondSection />
|
||||
<FeatureOne />
|
||||
<FeatureTwo />
|
||||
<FeatureThree />
|
||||
<Faq />
|
||||
<Flex
|
||||
direction={'column'}
|
||||
maxW="100%"
|
||||
mx="auto"
|
||||
mb={{ base: '60px', md: '60px' }}
|
||||
>
|
||||
<Text
|
||||
color="navy.700"
|
||||
fontWeight={'800'}
|
||||
fontSize="30px"
|
||||
mb="20px"
|
||||
textAlign={'center'}
|
||||
>
|
||||
Join our newsletter
|
||||
</Text>
|
||||
<Text
|
||||
fontWeight={'500'}
|
||||
fontSize={{ base: '15px', md: 'lg' }}
|
||||
color="gray.600"
|
||||
px={{ base: '10px', md: '0px' }}
|
||||
mb="30px"
|
||||
textAlign={'center'}
|
||||
>
|
||||
By subscribing, you'll be the first to know about the latest news
|
||||
and updates.
|
||||
</Text>
|
||||
<form
|
||||
id="form-fbcaaaec-e795-4419-b112-06934fd0051d"
|
||||
action="https://api.encharge.io/v1/forms/fbcaaaec-e795-4419-b112-06934fd0051d/submission/plain"
|
||||
method="POST"
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexWrap: 'wrap'
|
||||
}}
|
||||
>
|
||||
<div className="sc-jzJRlG kfekry">
|
||||
<div className="encharge-form-group sc-jTzLTM frRvjZ form-group">
|
||||
<Input
|
||||
variant="main"
|
||||
placeholder="Enter your email*"
|
||||
me="14px"
|
||||
h="100%"
|
||||
isRequired={true}
|
||||
w={{ base: '96%', md: '420px' }}
|
||||
maxW="100%"
|
||||
fontWeight="500"
|
||||
_placeholder={{
|
||||
color: 'gray.600',
|
||||
fontWeight: '500'
|
||||
}}
|
||||
borderRadius="70px"
|
||||
mb="0px !important"
|
||||
type="email"
|
||||
id="31b6ea2a-d9c7-4b42-9a01-7677838f07e9"
|
||||
name="email"
|
||||
className="encharge-form-input sc-kAzzGY kTMZCx form-control"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sc-cSHVUG ebRkVm">
|
||||
<Button
|
||||
py="20px"
|
||||
px="40px"
|
||||
fontSize="sm"
|
||||
fontWeight={'600'}
|
||||
variant="primary"
|
||||
borderRadius="45px"
|
||||
bg="linear-gradient(15.46deg, #4A25E1 26.3%, #7B5AFF 86.4%) !important"
|
||||
w={{ base: '100%', md: '150px' }}
|
||||
h="58px"
|
||||
type="submit"
|
||||
className="encharge-form-submit-button sc-gZMcBi ejYzxT btn btn-primary"
|
||||
>
|
||||
Subscribe
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<FooterWebsite />
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
// eslint-disabled
|
||||
|
||||
import { Icon, Flex, Text, useColorModeValue } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
import { MdChevronRight } from 'react-icons/md';
|
||||
|
||||
export default function SecondSection() {
|
||||
const brandColorPrice = useColorModeValue('brand.500', 'white');
|
||||
const textColor = useColorModeValue('#120F43', 'white');
|
||||
return (
|
||||
<Flex
|
||||
id="features"
|
||||
zIndex="2"
|
||||
w="100%"
|
||||
direction={{ base: 'column' }}
|
||||
bgSize="cover"
|
||||
alignItems="center"
|
||||
position="relative"
|
||||
pt={{ base: '90px', md: '100px', xl: '140px' }}
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
px={{ base: '0px', md: '40px', xl: '0px' }}
|
||||
maxW="1170px"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
>
|
||||
<Flex direction="column" width="stretch">
|
||||
<Flex
|
||||
direction="column"
|
||||
mx="auto"
|
||||
alignItems="center"
|
||||
textAlign="center"
|
||||
>
|
||||
<Text
|
||||
as="h3"
|
||||
textAlign={{ base: 'center', lg: 'center' }}
|
||||
fontWeight="700"
|
||||
letterSpacing="2px"
|
||||
color={brandColorPrice}
|
||||
fontSize={{ base: 'xs', md: 'md' }}
|
||||
w="100%"
|
||||
mb="10px"
|
||||
>
|
||||
HOW IT WORKS
|
||||
</Text>
|
||||
<Text
|
||||
as="h2"
|
||||
textAlign={{ base: 'center' }}
|
||||
color={textColor}
|
||||
fontWeight="800"
|
||||
fontSize={{ base: '30px', md: '48px', lg: '48px', xl: '58px' }}
|
||||
lineHeight={{ base: '38px', md: '60px', lg: '60px', xl: '70px' }}
|
||||
mb={{ base: '20px', md: '30px' }}
|
||||
mx="auto"
|
||||
>
|
||||
How it works?
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex
|
||||
w={{ base: '86%', md: '76%', lg: '100%' }}
|
||||
pt={{ base: '40px', md: '60px', xl: '60px' }}
|
||||
maxW="100%"
|
||||
flexDirection={{ base: 'column', lg: 'row' }}
|
||||
align={'center'}
|
||||
justify="center"
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
me={{ base: '0px', lg: '32px' }}
|
||||
mb={{ base: '50px', lg: '0px' }}
|
||||
>
|
||||
<Text
|
||||
as="h3"
|
||||
color="navy.700"
|
||||
fontWeight={'800'}
|
||||
textAlign={{ base: 'center', md: 'left' }}
|
||||
fontSize="20px"
|
||||
mb="12px"
|
||||
>
|
||||
Step 1: This is an example
|
||||
</Text>
|
||||
<Text
|
||||
color="gray.600"
|
||||
fontWeight={'500'}
|
||||
fontSize={{ base: '15px', md: 'md' }}
|
||||
textAlign={{ base: 'center', md: 'left' }}
|
||||
lineHeight={{ base: '28px', md: '30px' }}
|
||||
w="98%"
|
||||
>
|
||||
This is where your first step paragraph goes. For the moment, this
|
||||
is just an example to see what it will look like.
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
justify="center"
|
||||
align="center"
|
||||
bg="white"
|
||||
boxShadow={'0px 10.83px 28px -2px rgba(203, 213, 224, 0.79)'}
|
||||
borderRadius="50px"
|
||||
display={{ base: 'none', lg: 'flex' }}
|
||||
me={{ base: '0px', lg: '32px' }}
|
||||
minW="38px"
|
||||
minH="38px"
|
||||
>
|
||||
<Icon
|
||||
w="23px"
|
||||
h="23px"
|
||||
color="#7B5AFF"
|
||||
bg="transparent"
|
||||
as={MdChevronRight}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex
|
||||
direction="column"
|
||||
me={{ base: '0px', lg: '20px' }}
|
||||
mb={{ base: '50px', lg: '0px' }}
|
||||
>
|
||||
<Text
|
||||
as="h3"
|
||||
color="navy.700"
|
||||
fontWeight={'800'}
|
||||
fontSize="20px"
|
||||
mb="12px"
|
||||
textAlign={{ base: 'center', md: 'left' }}
|
||||
>
|
||||
Step 2: This is another example
|
||||
</Text>
|
||||
<Text
|
||||
color="gray.600"
|
||||
fontWeight={'500'}
|
||||
fontSize={{ base: '15px', md: 'md' }}
|
||||
textAlign={{ base: 'center', md: 'left' }}
|
||||
lineHeight={{ base: '28px', md: '30px' }}
|
||||
w="98%"
|
||||
>
|
||||
This is where your second step paragraph goes. For the moment,
|
||||
this is just an example to see what it will look like.
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
justify="center"
|
||||
align="center"
|
||||
bg="white"
|
||||
boxShadow={'0px 10.83px 28px -2px rgba(203, 213, 224, 0.79)'}
|
||||
borderRadius="50px"
|
||||
display={{ base: 'none', lg: 'flex' }}
|
||||
me={{ base: '0px', lg: '32px' }}
|
||||
minW="38px"
|
||||
minH="38px"
|
||||
>
|
||||
<Icon
|
||||
w="23px"
|
||||
h="23px"
|
||||
color="#7B5AFF"
|
||||
bg="transparent"
|
||||
as={MdChevronRight}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex direction="column">
|
||||
<Text
|
||||
as="h3"
|
||||
color="navy.700"
|
||||
fontWeight={'800'}
|
||||
fontSize="20px"
|
||||
mb="12px"
|
||||
textAlign={{ base: 'center', md: 'left' }}
|
||||
>
|
||||
Step 3: This is an example too
|
||||
</Text>
|
||||
<Text
|
||||
color="gray.600"
|
||||
fontWeight={'500'}
|
||||
fontSize={{ base: '15px', md: 'md' }}
|
||||
textAlign={{ base: 'center', md: 'left' }}
|
||||
lineHeight={{ base: '28px', md: '30px' }}
|
||||
>
|
||||
This is where your third step paragraph goes. For the moment, this
|
||||
is just an example to see what it will look like.
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
105
boilerplate-chakra-pro-main/components/layout/index.tsx
Normal file
105
boilerplate-chakra-pro-main/components/layout/index.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import Footer from '@/components/footer/FooterAdmin';
|
||||
import Navbar from '@/components/navbar/NavbarAdmin';
|
||||
import { routes } from '@/components/routes';
|
||||
import Sidebar from '@/components/sidebar/Sidebar';
|
||||
import { Database } from '@/types_db';
|
||||
import { getActiveRoute } from '@/utils/navigation';
|
||||
import { Box, useDisclosure } from '@chakra-ui/react';
|
||||
import { User } from '@supabase/supabase-js';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import {
|
||||
PlanContext,
|
||||
OpenContext,
|
||||
ProductsContext,
|
||||
SubscriptionContext,
|
||||
UserContext,
|
||||
UserDetailsContext,
|
||||
ApiKeyContext
|
||||
} from '@/contexts/layout';
|
||||
import React 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 {
|
||||
children: React.ReactNode;
|
||||
title: string;
|
||||
description: string;
|
||||
user: User | null | undefined;
|
||||
products: ProductWithPrices[];
|
||||
subscription: SubscriptionWithProduct | null;
|
||||
userDetails: { [x: string]: any } | null;
|
||||
}
|
||||
|
||||
const DashboardLayout: React.FC<Props> = (props: Props) => {
|
||||
const pathname = usePathname();
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [plan, setPlan] = React.useState({
|
||||
product: 'prod_QfhYC6AAtI5IKW',
|
||||
price: 'price_1PoM9GDWNoHJSR0zmwpicH8y'
|
||||
});
|
||||
const [apiKey, setApiKey] = React.useState('default');
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
return (
|
||||
<UserContext.Provider value={props.user}>
|
||||
<UserDetailsContext.Provider value={props.user}>
|
||||
<OpenContext.Provider value={{ open, setOpen }}>
|
||||
<PlanContext.Provider value={{ plan, setPlan }}>
|
||||
<ProductsContext.Provider value={props.products}>
|
||||
<SubscriptionContext.Provider value={props.subscription}>
|
||||
<ApiKeyContext.Provider value={{ apiKey, setApiKey }}>
|
||||
<Box>
|
||||
<Sidebar routes={routes} />
|
||||
<Box
|
||||
pt={{ base: '70px', md: '80px' }}
|
||||
float="right"
|
||||
minHeight="100vh"
|
||||
height="100%"
|
||||
overflow="auto"
|
||||
position="relative"
|
||||
maxHeight="100%"
|
||||
w={{ base: '100%', xl: 'calc( 100% - 290px )' }}
|
||||
maxWidth={{ base: '100%', xl: 'calc( 100% - 290px )' }}
|
||||
transition="all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1)"
|
||||
transitionDuration=".2s, .2s, .35s"
|
||||
transitionProperty="top, bottom, width"
|
||||
transitionTimingFunction="linear, linear, ease"
|
||||
>
|
||||
<Navbar
|
||||
onOpen={onOpen}
|
||||
logoText={'Horizon UI Boilerplate'}
|
||||
userDetails={props.userDetails}
|
||||
brandText={getActiveRoute(routes, pathname)}
|
||||
/>
|
||||
<Box
|
||||
mx="auto"
|
||||
p={{ base: '20px', md: '30px' }}
|
||||
pe="20px"
|
||||
minH="100vh"
|
||||
>
|
||||
{props.children}
|
||||
</Box>
|
||||
<Footer />
|
||||
</Box>
|
||||
</Box>
|
||||
</ApiKeyContext.Provider>
|
||||
</SubscriptionContext.Provider>
|
||||
</ProductsContext.Provider>
|
||||
</PlanContext.Provider>
|
||||
</OpenContext.Provider>
|
||||
</UserDetailsContext.Provider>
|
||||
</UserContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardLayout;
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import React from 'react';
|
||||
|
||||
export default function InnerContent(props) {
|
||||
const { children, ...rest } = props;
|
||||
return (
|
||||
<Flex
|
||||
direction={{ base: 'column' }}
|
||||
maxW={{ xl: '1170px' }}
|
||||
align="center"
|
||||
mx="auto"
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
19
boilerplate-chakra-pro-main/components/link/Link.tsx
Normal file
19
boilerplate-chakra-pro-main/components/link/Link.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
'use client';
|
||||
import { ButtonProps } from '@chakra-ui/react';
|
||||
import NextLink, { LinkProps as NextLinkProps } from 'next/link';
|
||||
|
||||
import { Button } from '@chakra-ui/react';
|
||||
|
||||
type LinkProps = ButtonProps & NextLinkProps;
|
||||
|
||||
function Link({ href, children, ...props }: LinkProps) {
|
||||
return (
|
||||
<NextLink href={href} passHref legacyBehavior>
|
||||
<Button as="a" variant="a" {...props}>
|
||||
{children}
|
||||
</Button>
|
||||
</NextLink>
|
||||
);
|
||||
}
|
||||
|
||||
export default Link;
|
||||
20
boilerplate-chakra-pro-main/components/link/LinkButton.tsx
Normal file
20
boilerplate-chakra-pro-main/components/link/LinkButton.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
'use client';
|
||||
import Link, { LinkProps } from 'next/link';
|
||||
import { Button, ButtonProps } from '@chakra-ui/react';
|
||||
|
||||
type ChakraAndNextProps = ButtonProps & LinkProps;
|
||||
|
||||
export default function LinkButton({
|
||||
href,
|
||||
children,
|
||||
prefetch = true,
|
||||
...props
|
||||
}: ChakraAndNextProps) {
|
||||
return (
|
||||
<Link href={href} passHref prefetch={prefetch}>
|
||||
<Button as="a" variant="a" {...props}>
|
||||
{children}
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
31
boilerplate-chakra-pro-main/components/link/NavLink.tsx
Normal file
31
boilerplate-chakra-pro-main/components/link/NavLink.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
'use client';
|
||||
import NextLink, { LinkProps as NextLinkProps } from 'next/link';
|
||||
import {
|
||||
CSSProperties,
|
||||
ComponentProps,
|
||||
PropsWithChildren,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
|
||||
export type NavLinkProps = NextLinkProps &
|
||||
PropsWithChildren & {
|
||||
styles?: CSSProperties;
|
||||
borderRadius?: ComponentProps<typeof NextLink>['style'];
|
||||
};
|
||||
|
||||
function NavLink({ children, styles, ...props }: NavLinkProps) {
|
||||
const memoizedStyles = useMemo(
|
||||
() => ({
|
||||
...styles,
|
||||
}),
|
||||
[styles],
|
||||
);
|
||||
|
||||
return (
|
||||
<NextLink style={memoizedStyles} {...props}>
|
||||
{children}
|
||||
</NextLink>
|
||||
);
|
||||
}
|
||||
|
||||
export default NavLink;
|
||||
43
boilerplate-chakra-pro-main/components/menu/ItemContent.tsx
Normal file
43
boilerplate-chakra-pro-main/components/menu/ItemContent.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
// chakra imports
|
||||
import { Icon, Flex, Text, useColorModeValue } from '@chakra-ui/react';
|
||||
import { MdUpgrade } from 'react-icons/md';
|
||||
|
||||
export function ItemContent(props: { info: string }) {
|
||||
const textColor = useColorModeValue('navy.700', 'white');
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
justify="center"
|
||||
align="center"
|
||||
borderRadius="16px"
|
||||
minH={{ base: '60px', md: '70px' }}
|
||||
h={{ base: '60px', md: '70px' }}
|
||||
minW={{ base: '60px', md: '70px' }}
|
||||
w={{ base: '60px', md: '70px' }}
|
||||
me="14px"
|
||||
bgGradient="linear(to-b, brand.400, brand.600)"
|
||||
>
|
||||
<Icon as={MdUpgrade} color="white" w={8} h={14} />
|
||||
</Flex>
|
||||
<Flex flexDirection="column">
|
||||
<Text
|
||||
mb="5px"
|
||||
fontWeight="bold"
|
||||
color={textColor}
|
||||
fontSize={{ base: 'md', md: 'md' }}
|
||||
>
|
||||
New Update: {props.info}
|
||||
</Text>
|
||||
<Flex alignItems="center">
|
||||
<Text
|
||||
fontSize={{ base: 'sm', md: 'sm' }}
|
||||
lineHeight="100%"
|
||||
color={textColor}
|
||||
>
|
||||
A new update for your downloaded item is available!
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
}
|
||||
149
boilerplate-chakra-pro-main/components/menu/MainMenu.tsx
Normal file
149
boilerplate-chakra-pro-main/components/menu/MainMenu.tsx
Normal file
@@ -0,0 +1,149 @@
|
||||
// Chakra imports
|
||||
import {
|
||||
Icon,
|
||||
Flex,
|
||||
Text,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
useDisclosure,
|
||||
useColorModeValue
|
||||
} from '@chakra-ui/react';
|
||||
// Assets
|
||||
import {
|
||||
MdOutlineMoreHoriz,
|
||||
MdOutlinePerson,
|
||||
MdOutlineCardTravel,
|
||||
MdOutlineLightbulb,
|
||||
MdOutlineSettings
|
||||
} from 'react-icons/md';
|
||||
|
||||
export default function Banner(props: { [x: string]: any }) {
|
||||
const { ...rest } = props;
|
||||
|
||||
const textColor = useColorModeValue('secondaryGray.500', 'white');
|
||||
const textHover = useColorModeValue(
|
||||
{ color: '#120F43', bg: 'unset' },
|
||||
{ color: 'secondaryGray.500', bg: 'unset' }
|
||||
);
|
||||
const iconColor = useColorModeValue('brand.500', 'white');
|
||||
const bgList = useColorModeValue('white', 'whiteAlpha.100');
|
||||
const bgShadow = useColorModeValue('14px 17px 40px 4px rgba(112, 144, 176, 0.08)', 'unset');
|
||||
const bgButton = useColorModeValue('secondaryGray.300', 'whiteAlpha.100');
|
||||
const bgHover = useColorModeValue({ bg: 'secondaryGray.400' }, { bg: 'whiteAlpha.50' });
|
||||
const bgFocus = useColorModeValue({ bg: 'secondaryGray.300' }, { bg: 'whiteAlpha.100' });
|
||||
|
||||
// Ellipsis modals
|
||||
const { isOpen: isOpen1, onOpen: onOpen1, onClose: onClose1 } = useDisclosure();
|
||||
|
||||
return (
|
||||
<Menu isOpen={isOpen1} onClose={onClose1}>
|
||||
<MenuButton
|
||||
alignItems='center'
|
||||
justifyContent='center'
|
||||
bg={bgButton}
|
||||
_hover={bgHover}
|
||||
_focus={bgFocus}
|
||||
_active={bgFocus}
|
||||
w='37px'
|
||||
h='37px'
|
||||
lineHeight='100%'
|
||||
onClick={onOpen1}
|
||||
borderRadius='10px'
|
||||
{...rest}>
|
||||
<Icon as={MdOutlineMoreHoriz} color={iconColor} w='24px' h='24px' mt='4px' />
|
||||
</MenuButton>
|
||||
<MenuList
|
||||
w='150px'
|
||||
minW='unset'
|
||||
maxW='150px !important'
|
||||
border='transparent'
|
||||
backdropFilter='blur(63px)'
|
||||
bg={bgList}
|
||||
boxShadow={bgShadow}
|
||||
borderRadius='20px'
|
||||
p='15px'>
|
||||
<MenuItem
|
||||
transition='0.2s linear'
|
||||
color={textColor}
|
||||
_hover={textHover}
|
||||
p='0px'
|
||||
borderRadius='8px'
|
||||
_active={{
|
||||
bg: 'transparent'
|
||||
}}
|
||||
_focus={{
|
||||
bg: 'transparent'
|
||||
}}
|
||||
mb='10px'>
|
||||
<Flex align='center'>
|
||||
<Icon as={MdOutlinePerson} h='16px' w='16px' me='8px' />
|
||||
<Text fontSize='sm' fontWeight='400'>
|
||||
Panel 1
|
||||
</Text>
|
||||
</Flex>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
transition='0.2s linear'
|
||||
p='0px'
|
||||
borderRadius='8px'
|
||||
color={textColor}
|
||||
_hover={textHover}
|
||||
_active={{
|
||||
bg: 'transparent'
|
||||
}}
|
||||
_focus={{
|
||||
bg: 'transparent'
|
||||
}}
|
||||
mb='10px'>
|
||||
<Flex align='center'>
|
||||
<Icon as={MdOutlineCardTravel} h='16px' w='16px' me='8px' />
|
||||
<Text fontSize='sm' fontWeight='400'>
|
||||
Panel 2
|
||||
</Text>
|
||||
</Flex>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
transition='0.2s linear'
|
||||
p='0px'
|
||||
borderRadius='8px'
|
||||
color={textColor}
|
||||
_hover={textHover}
|
||||
_active={{
|
||||
bg: 'transparent'
|
||||
}}
|
||||
_focus={{
|
||||
bg: 'transparent'
|
||||
}}
|
||||
mb='10px'>
|
||||
<Flex align='center'>
|
||||
<Icon as={MdOutlineLightbulb} h='16px' w='16px' me='8px' />
|
||||
<Text fontSize='sm' fontWeight='400'>
|
||||
Panel 3
|
||||
</Text>
|
||||
</Flex>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
transition='0.2s linear'
|
||||
color={textColor}
|
||||
_hover={textHover}
|
||||
p='0px'
|
||||
borderRadius='8px'
|
||||
_active={{
|
||||
bg: 'transparent'
|
||||
}}
|
||||
_focus={{
|
||||
bg: 'transparent'
|
||||
}}>
|
||||
<Flex align='center'>
|
||||
<Icon as={MdOutlineSettings} h='16px' w='16px' me='8px' />
|
||||
<Text fontSize='sm' fontWeight='400'>
|
||||
Panel 4
|
||||
</Text>
|
||||
</Flex>
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
128
boilerplate-chakra-pro-main/components/menu/TransparentMenu.tsx
Normal file
128
boilerplate-chakra-pro-main/components/menu/TransparentMenu.tsx
Normal file
@@ -0,0 +1,128 @@
|
||||
// Chakra imports
|
||||
import {
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
useDisclosure,
|
||||
useColorModeValue,
|
||||
Flex,
|
||||
Icon,
|
||||
Text
|
||||
} from '@chakra-ui/react';
|
||||
// Assets
|
||||
import { MdOutlinePerson, MdOutlineCardTravel, MdOutlineLightbulb, MdOutlineSettings } from 'react-icons/md';
|
||||
export default function Banner(props: { icon: JSX.Element | string; [x: string]: any }) {
|
||||
const { icon, ...rest } = props;
|
||||
|
||||
// Ellipsis modals
|
||||
const { isOpen: isOpen1, onOpen: onOpen1, onClose: onClose1 } = useDisclosure();
|
||||
|
||||
// Chakra color mode
|
||||
|
||||
const textColor = useColorModeValue('secondaryGray.500', 'white');
|
||||
const textHover = useColorModeValue(
|
||||
{ color: '#120F43', bg: 'unset' },
|
||||
{ color: 'secondaryGray.500', bg: 'unset' }
|
||||
);
|
||||
const bgList = useColorModeValue('white', 'whiteAlpha.100');
|
||||
const bgShadow = useColorModeValue('14px 17px 40px 4px rgba(112, 144, 176, 0.08)', 'unset');
|
||||
|
||||
return (
|
||||
<Menu isOpen={isOpen1} onClose={onClose1}>
|
||||
<MenuButton {...rest} onClick={onOpen1}>
|
||||
{icon}
|
||||
</MenuButton>
|
||||
<MenuList
|
||||
w='150px'
|
||||
minW='unset'
|
||||
maxW='150px !important'
|
||||
border='transparent'
|
||||
backdropFilter='blur(63px)'
|
||||
bg={bgList}
|
||||
boxShadow={bgShadow}
|
||||
borderRadius='20px'
|
||||
p='15px'>
|
||||
<MenuItem
|
||||
transition='0.2s linear'
|
||||
color={textColor}
|
||||
_hover={textHover}
|
||||
p='0px'
|
||||
borderRadius='8px'
|
||||
_active={{
|
||||
bg: 'transparent'
|
||||
}}
|
||||
_focus={{
|
||||
bg: 'transparent'
|
||||
}}
|
||||
mb='10px'>
|
||||
<Flex align='center'>
|
||||
<Icon as={MdOutlinePerson} h='16px' w='16px' me='8px' />
|
||||
<Text fontSize='sm' fontWeight='400'>
|
||||
Panel 1
|
||||
</Text>
|
||||
</Flex>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
transition='0.2s linear'
|
||||
p='0px'
|
||||
borderRadius='8px'
|
||||
color={textColor}
|
||||
_hover={textHover}
|
||||
_active={{
|
||||
bg: 'transparent'
|
||||
}}
|
||||
_focus={{
|
||||
bg: 'transparent'
|
||||
}}
|
||||
mb='10px'>
|
||||
<Flex align='center'>
|
||||
<Icon as={MdOutlineCardTravel} h='16px' w='16px' me='8px' />
|
||||
<Text fontSize='sm' fontWeight='400'>
|
||||
Panel 2
|
||||
</Text>
|
||||
</Flex>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
transition='0.2s linear'
|
||||
p='0px'
|
||||
borderRadius='8px'
|
||||
color={textColor}
|
||||
_hover={textHover}
|
||||
_active={{
|
||||
bg: 'transparent'
|
||||
}}
|
||||
_focus={{
|
||||
bg: 'transparent'
|
||||
}}
|
||||
mb='10px'>
|
||||
<Flex align='center'>
|
||||
<Icon as={MdOutlineLightbulb} h='16px' w='16px' me='8px' />
|
||||
<Text fontSize='sm' fontWeight='400'>
|
||||
Panel 3
|
||||
</Text>
|
||||
</Flex>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
transition='0.2s linear'
|
||||
color={textColor}
|
||||
_hover={textHover}
|
||||
p='0px'
|
||||
borderRadius='8px'
|
||||
_active={{
|
||||
bg: 'transparent'
|
||||
}}
|
||||
_focus={{
|
||||
bg: 'transparent'
|
||||
}}>
|
||||
<Flex align='center'>
|
||||
<Icon as={MdOutlineSettings} h='16px' w='16px' me='8px' />
|
||||
<Text fontSize='sm' fontWeight='400'>
|
||||
Panel 4
|
||||
</Text>
|
||||
</Flex>
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
157
boilerplate-chakra-pro-main/components/navbar/NavbarAdmin.tsx
Normal file
157
boilerplate-chakra-pro-main/components/navbar/NavbarAdmin.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
'use client';
|
||||
|
||||
/* eslint-disable */
|
||||
import AdminNavbarLinks from './NavbarLinksAdmin';
|
||||
import { isWindowAvailable } from '@/utils/navigation';
|
||||
import {
|
||||
Box,
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
Flex,
|
||||
Link,
|
||||
useColorModeValue
|
||||
} from '@chakra-ui/react';
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
export default function AdminNavbar(props: {
|
||||
logoText: string;
|
||||
brandText: string;
|
||||
userDetails: { [x: string]: any } | null;
|
||||
onOpen: (...args: any[]) => any;
|
||||
[x: string]: any;
|
||||
}) {
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
isWindowAvailable() && window.addEventListener('scroll', changeNavbar);
|
||||
|
||||
return () => {
|
||||
isWindowAvailable() && window.removeEventListener('scroll', changeNavbar);
|
||||
};
|
||||
});
|
||||
|
||||
const { brandText, userDetails } = props;
|
||||
|
||||
// Here are all the props that may change depending on navbar's type or state.(secondary, variant, scrolled)
|
||||
let mainText = useColorModeValue('#120F43', 'white');
|
||||
let secondaryText = useColorModeValue('gray.700', 'white');
|
||||
let navbarPosition = 'fixed' as const;
|
||||
let navbarFilter = 'none';
|
||||
let navbarBackdrop = 'blur(20px)';
|
||||
let navbarShadow = 'none';
|
||||
let navbarBg = useColorModeValue(
|
||||
'rgba(244, 247, 254, 0.2)',
|
||||
'rgba(11,20,55,0.5)'
|
||||
);
|
||||
let navbarBorder = 'transparent';
|
||||
let secondaryMargin = '0px';
|
||||
let gap = '0px';
|
||||
const changeNavbar = () => {
|
||||
if (isWindowAvailable() && window.scrollY > 1) {
|
||||
setScrolled(true);
|
||||
} else {
|
||||
setScrolled(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
zIndex="100"
|
||||
position={navbarPosition}
|
||||
boxShadow={navbarShadow}
|
||||
bg={navbarBg}
|
||||
borderColor={navbarBorder}
|
||||
filter={navbarFilter}
|
||||
backdropFilter={navbarBackdrop}
|
||||
backgroundPosition="center"
|
||||
backgroundSize="cover"
|
||||
borderRadius="16px"
|
||||
borderWidth="1.5px"
|
||||
borderStyle="solid"
|
||||
transitionDelay="0s, 0s, 0s, 0s"
|
||||
transitionDuration=" 0.25s, 0.25s, 0.25s, 0s"
|
||||
transition-property="box-shadow, background-color, filter, border"
|
||||
transitionTimingFunction="linear, linear, linear, linear"
|
||||
alignItems={{ xl: 'center' }}
|
||||
display={'flex'}
|
||||
minH="75px"
|
||||
justifyContent={{ xl: 'center' }}
|
||||
lineHeight="25.6px"
|
||||
mx="auto"
|
||||
mt={secondaryMargin}
|
||||
pb="8px"
|
||||
right={{ base: '12px', md: '30px', lg: '30px', xl: '30px' }}
|
||||
ps={{
|
||||
base: '8px',
|
||||
md: '12px'
|
||||
}}
|
||||
pt="8px"
|
||||
top={{ base: '12px', md: '16px', xl: '18px' }}
|
||||
w={{
|
||||
base: 'calc(100vw - 8%)',
|
||||
md: 'calc(100vw - 8%)',
|
||||
lg: 'calc(100vw - 6%)',
|
||||
xl: 'calc(100vw - 350px)',
|
||||
'2xl': 'calc(100vw - 365px)'
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
w="100%"
|
||||
flexDirection={{
|
||||
base: 'row',
|
||||
md: 'row'
|
||||
}}
|
||||
alignItems={{ xl: 'center' }}
|
||||
mb={gap}
|
||||
>
|
||||
<Box>
|
||||
<Breadcrumb>
|
||||
<BreadcrumbItem
|
||||
color={secondaryText}
|
||||
fontSize={{ base: 'xs', md: 'sm' }}
|
||||
mb={{ base: '0px', md: '5px' }}
|
||||
>
|
||||
<BreadcrumbLink href="#" color={secondaryText}>
|
||||
Pages
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
|
||||
<BreadcrumbItem
|
||||
color={secondaryText}
|
||||
fontSize={{ base: 'xs', md: 'sm' }}
|
||||
>
|
||||
<BreadcrumbLink href="#" color={secondaryText}>
|
||||
{brandText}
|
||||
</BreadcrumbLink>
|
||||
</BreadcrumbItem>
|
||||
</Breadcrumb>
|
||||
{/* Here we create navbar brand, based on route name */}
|
||||
<Link
|
||||
color={mainText}
|
||||
href="#"
|
||||
bg="inherit"
|
||||
borderRadius="inherit"
|
||||
fontWeight="bold"
|
||||
fontSize={{ base: 'lg', md: '34px' }}
|
||||
p="0px"
|
||||
_hover={{ color: { mainText } }}
|
||||
_active={{
|
||||
bg: 'inherit',
|
||||
transform: 'none',
|
||||
borderColor: 'transparent'
|
||||
}}
|
||||
_focus={{
|
||||
boxShadow: 'none'
|
||||
}}
|
||||
>
|
||||
{brandText}
|
||||
</Link>
|
||||
</Box>
|
||||
<Box ms="auto" w={{ sm: '154px', md: 'unset' }}>
|
||||
<AdminNavbarLinks />
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
403
boilerplate-chakra-pro-main/components/navbar/NavbarFixed.js
Normal file
403
boilerplate-chakra-pro-main/components/navbar/NavbarFixed.js
Normal file
@@ -0,0 +1,403 @@
|
||||
/* eslint-disable */
|
||||
'use client';
|
||||
|
||||
import logo from '/public/logo-horizon-boilerplate.png';
|
||||
import {
|
||||
Image,
|
||||
Button,
|
||||
Flex,
|
||||
Icon,
|
||||
Link,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
Text,
|
||||
useColorModeValue,
|
||||
} from '@chakra-ui/react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { IoIosStar, IoMdTrophy } from 'react-icons/io';
|
||||
import { IoMenuOutline } from 'react-icons/io5';
|
||||
import { MdChevronRight, MdPrivacyTip } from 'react-icons/md';
|
||||
|
||||
export default function AdminNavbar(props) {
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('scroll', changeNavbar);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', changeNavbar);
|
||||
};
|
||||
});
|
||||
const { secondary, message } = props;
|
||||
|
||||
// Here are all the props that may change depending on navbar's type or state.(secondary, variant, scrolled)
|
||||
let textColor = useColorModeValue('#120F43', 'white');
|
||||
let borderColor = useColorModeValue('gray.300', 'white');
|
||||
let navbarPosition = 'fixed';
|
||||
let navbarFilter = 'none';
|
||||
let navbarShadow = '45px 76px 113px 7px rgba(112, 144, 176, 0.08)';
|
||||
let navbarBorder = 'transparent';
|
||||
let paddingX = '15px';
|
||||
let gap = '0px';
|
||||
let navbarTop = '0px';
|
||||
let menuBg = useColorModeValue('white', 'navy.800');
|
||||
const changeNavbar = () => {
|
||||
if (window.scrollY > 1) {
|
||||
setScrolled(true);
|
||||
} else {
|
||||
setScrolled(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Flex
|
||||
position={navbarPosition}
|
||||
boxShadow={navbarShadow}
|
||||
bg="white"
|
||||
direction="column"
|
||||
borderColor={navbarBorder}
|
||||
filter={navbarFilter}
|
||||
backgroundPosition="center"
|
||||
backgroundSize="cover"
|
||||
zIndex="200"
|
||||
borderStyle="solid"
|
||||
alignItems={{ xl: 'center' }}
|
||||
display={secondary ? 'block' : 'flex'}
|
||||
justifyContent={{ xl: 'center' }}
|
||||
lineHeight="25.6px"
|
||||
mx="auto"
|
||||
left="50%"
|
||||
transform="translate(-50%,0px)"
|
||||
w="100%"
|
||||
top={navbarTop}
|
||||
>
|
||||
{/* Misc */}
|
||||
|
||||
<Flex
|
||||
w="100%"
|
||||
display={{ base: 'none', lg: 'flex' }}
|
||||
bg="#F3F5FA"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Flex
|
||||
w={{
|
||||
base: 'calc(100vw - 4%)',
|
||||
md: 'calc(100vw - 4%)',
|
||||
lg: '100vw',
|
||||
xl: 'calc(100vw - 250px)',
|
||||
'2xl': '1200px',
|
||||
}}
|
||||
px={{
|
||||
sm: paddingX,
|
||||
md: '10px',
|
||||
lg: '12px',
|
||||
}}
|
||||
py="4px"
|
||||
gap="40px"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Flex direction="row" alignItems="center">
|
||||
<Icon
|
||||
me="6px"
|
||||
w="12px"
|
||||
h="12px"
|
||||
color="brand.500"
|
||||
as={MdPrivacyTip}
|
||||
/>
|
||||
<Text fontSize="xs" fontWeight="500" h="100%" color="gray.500">
|
||||
Founded in EU. We respect your privacy.
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex direction="row" alignItems="center">
|
||||
<Icon me="1px" w="12px" h="12px" color="brand.500" as={IoIosStar} />
|
||||
<Icon me="1px" w="12px" h="12px" color="brand.500" as={IoIosStar} />
|
||||
<Icon me="1px" w="12px" h="12px" color="brand.500" as={IoIosStar} />
|
||||
<Icon me="1px" w="12px" h="12px" color="brand.500" as={IoIosStar} />
|
||||
<Icon me="6px" w="12px" h="12px" color="brand.500" as={IoIosStar} />
|
||||
<Text fontSize="xs" fontWeight="500" h="100%" color="gray.500">
|
||||
Loved by 80,000+ users worldwide
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex direction="row" alignItems="center">
|
||||
<Icon
|
||||
me="6px"
|
||||
w="12px"
|
||||
h="12px"
|
||||
color="brand.500"
|
||||
as={IoMdTrophy}
|
||||
/>
|
||||
<Text fontSize="xs" fontWeight="500" h="100%" color="gray.500">
|
||||
#1 NextJS boilerplate & admin template
|
||||
|
||||
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
{/* Misc */}
|
||||
|
||||
<Flex
|
||||
w={{
|
||||
base: 'calc(100vw - 4%)',
|
||||
md: 'calc(100vw - 4%)',
|
||||
lg: '100vw',
|
||||
xl: 'calc(100vw - 250px)',
|
||||
'2xl': '1200px',
|
||||
}}
|
||||
px={{
|
||||
sm: paddingX,
|
||||
md: '10px',
|
||||
lg: '12px',
|
||||
}}
|
||||
py="20px"
|
||||
ps={{
|
||||
xl: '12px',
|
||||
}}
|
||||
flexDirection={{
|
||||
sm: 'row',
|
||||
md: 'row',
|
||||
}}
|
||||
alignItems="center"
|
||||
justify="space-between"
|
||||
mb={gap}
|
||||
>
|
||||
<Link
|
||||
display={'flex'}
|
||||
alignItems="center"
|
||||
justifyContent={'center'}
|
||||
href="/"
|
||||
>
|
||||
<Image
|
||||
alt=" "
|
||||
w="36px"
|
||||
src={logo.src}
|
||||
/>
|
||||
<Text ms="8px" me="10px" fontWeight="800" color="#120F43">
|
||||
Horizon UI Boilerplate
|
||||
</Text>
|
||||
</Link>
|
||||
<Flex>
|
||||
<Link
|
||||
display={{ base: 'none', lg: 'block' }}
|
||||
href="/dashboard/signin"
|
||||
color="gray.600"
|
||||
fontSize="sm"
|
||||
fontWeight="500"
|
||||
letterSpacing="0px"
|
||||
me="30px"
|
||||
my="auto"
|
||||
>
|
||||
Generator
|
||||
</Link>
|
||||
<Link
|
||||
display={{ base: 'none', lg: 'block' }}
|
||||
href="/#features"
|
||||
color="gray.600"
|
||||
fontSize="sm"
|
||||
fontWeight="500"
|
||||
letterSpacing="0px"
|
||||
me="30px"
|
||||
my="auto"
|
||||
>
|
||||
Features
|
||||
</Link>
|
||||
<Link
|
||||
display={{ base: 'none', lg: 'block' }}
|
||||
href="/pricing"
|
||||
color="gray.600"
|
||||
fontSize="sm"
|
||||
fontWeight="500"
|
||||
letterSpacing="0px"
|
||||
me="30px"
|
||||
my="auto"
|
||||
>
|
||||
Pricing
|
||||
</Link>
|
||||
<Link
|
||||
display={{ base: 'none', lg: 'block' }}
|
||||
href="#faqs"
|
||||
color="gray.600"
|
||||
fontSize="sm"
|
||||
fontWeight="500"
|
||||
letterSpacing="0px"
|
||||
me="30px"
|
||||
my="auto"
|
||||
>
|
||||
FAQs
|
||||
</Link>
|
||||
<Menu>
|
||||
<MenuButton
|
||||
display={{ base: 'block', xl: 'none' }}
|
||||
p="0px !important"
|
||||
maxH="20px"
|
||||
maxW="20px"
|
||||
alignContent="end"
|
||||
>
|
||||
<Icon
|
||||
display={{ base: 'block', lg: 'none' }}
|
||||
as={IoMenuOutline}
|
||||
color={textColor}
|
||||
w="20px"
|
||||
h="20px"
|
||||
_hover={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</MenuButton>
|
||||
<MenuList
|
||||
p="0px"
|
||||
mt="10px"
|
||||
borderRadius="10px"
|
||||
border="1px solid"
|
||||
borderColor="#CBD5E0"
|
||||
bg={menuBg}
|
||||
>
|
||||
<Flex flexDirection="column" p="10px">
|
||||
<MenuItem
|
||||
_hover={{ bg: 'none' }}
|
||||
_focus={{ bg: 'none' }}
|
||||
borderRadius="8px"
|
||||
px="14px"
|
||||
>
|
||||
<Link
|
||||
href="/dashboard/signin"
|
||||
color="gray.600"
|
||||
fontSize="md"
|
||||
fontWeight="500"
|
||||
me="30px"
|
||||
my="auto"
|
||||
>
|
||||
Generator
|
||||
</Link>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
_hover={{ bg: 'none' }}
|
||||
_focus={{ bg: 'none' }}
|
||||
borderRadius="8px"
|
||||
px="14px"
|
||||
>
|
||||
<Link
|
||||
href="/#features"
|
||||
color="gray.600"
|
||||
fontSize="md"
|
||||
fontWeight="500"
|
||||
me="30px"
|
||||
my="auto"
|
||||
>
|
||||
Features
|
||||
</Link>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
_hover={{ bg: 'none' }}
|
||||
_focus={{ bg: 'none' }}
|
||||
color="red.400"
|
||||
borderRadius="8px"
|
||||
>
|
||||
<Link
|
||||
href="/pricing"
|
||||
color="gray.600"
|
||||
fontSize="md"
|
||||
fontWeight="500"
|
||||
me="30px"
|
||||
my="auto"
|
||||
>
|
||||
Pricing
|
||||
</Link>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
_hover={{ bg: 'none' }}
|
||||
_focus={{ bg: 'none' }}
|
||||
color="red.400"
|
||||
borderRadius="8px"
|
||||
>
|
||||
<Link
|
||||
href="#faqs"
|
||||
color="gray.600"
|
||||
fontSize="md"
|
||||
fontWeight="500"
|
||||
me="30px"
|
||||
my="auto"
|
||||
>
|
||||
FAQs
|
||||
</Link>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
_hover={{ bg: 'none' }}
|
||||
_focus={{ bg: 'none' }}
|
||||
color="red.400"
|
||||
borderRadius="8px"
|
||||
>
|
||||
<Link
|
||||
href="/dashboard/signin"
|
||||
color={textColor}
|
||||
fontSize="md"
|
||||
fontWeight="600"
|
||||
me="18px"
|
||||
my="auto"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Login
|
||||
</Link>
|
||||
</MenuItem>
|
||||
<Button
|
||||
variant="transparent"
|
||||
border="1px solid"
|
||||
borderColor={borderColor}
|
||||
color={textColor}
|
||||
fontSize="md"
|
||||
borderRadius="45px"
|
||||
bg="transparent"
|
||||
my="auto"
|
||||
>
|
||||
<Link href="/dashboard/signin">
|
||||
Get started for Free
|
||||
<Icon
|
||||
as={MdChevronRight}
|
||||
mt="3px"
|
||||
ms="5px"
|
||||
h="16px"
|
||||
w="16px"
|
||||
/>
|
||||
</Link>
|
||||
</Button>
|
||||
</Flex>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
</Flex>
|
||||
<Flex display={{ base: 'none', lg: 'flex' }}>
|
||||
<Link
|
||||
href="/dashboard/signin"
|
||||
color={textColor}
|
||||
fontSize="sm"
|
||||
fontWeight="600"
|
||||
me="18px"
|
||||
letterSpacing="0px"
|
||||
my="auto"
|
||||
>
|
||||
Login
|
||||
</Link>
|
||||
|
||||
<Link href="/dashboard/signin">
|
||||
<Button
|
||||
variant="transparent"
|
||||
border="1px solid"
|
||||
borderColor={borderColor}
|
||||
color={textColor}
|
||||
fontSize="sm"
|
||||
borderRadius="45px"
|
||||
px="18px"
|
||||
py="12px"
|
||||
bg="transparent"
|
||||
my="auto"
|
||||
>
|
||||
{' '}
|
||||
Get started for Free
|
||||
<Icon as={MdChevronRight} mt="3px" h="16px" w="16px" />
|
||||
</Button>
|
||||
</Link>
|
||||
</Flex>
|
||||
</Flex>
|
||||
{secondary ? <Text color="white">{message}</Text> : null}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
'use client';
|
||||
|
||||
import { useSupabase } from '@/app/supabase-provider';
|
||||
import { routes } from '@/components/routes';
|
||||
import { SidebarResponsive } from '@/components/sidebar/Sidebar';
|
||||
import { OpenContext, UserContext } from '@/contexts/layout';
|
||||
import { handleRequest } from '@/utils/auth-helpers/client';
|
||||
import { SignOut } from '@/utils/auth-helpers/server';
|
||||
import { getRedirectMethod } from '@/utils/auth-helpers/settings';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Flex,
|
||||
Icon,
|
||||
Link,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuList,
|
||||
useColorModeValue
|
||||
} from '@chakra-ui/react';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { useContext } from 'react';
|
||||
import { MdOutlineLogout, MdHelpOutline } from 'react-icons/md';
|
||||
|
||||
export default function HeaderLinks(props?: { [x: string]: any }) {
|
||||
const { open, setOpen } = useContext(OpenContext);
|
||||
const user = useContext(UserContext);
|
||||
const router = getRedirectMethod() === 'client' ? useRouter() : null;
|
||||
// Chakra Color Mode
|
||||
const navbarIcon = useColorModeValue('gray.500', 'white');
|
||||
let menuBg = useColorModeValue('white', 'navy.800');
|
||||
const textColor = useColorModeValue('#120F43', 'white');
|
||||
const shadow = useColorModeValue(
|
||||
'14px 17px 40px 4px rgba(112, 144, 176, 0.18)',
|
||||
'0px 41px 75px #081132'
|
||||
);
|
||||
const buttonBg = useColorModeValue('transparent', 'navy.800');
|
||||
const hoverButton = useColorModeValue(
|
||||
{ bg: 'gray.100' },
|
||||
{ bg: 'whiteAlpha.100' }
|
||||
);
|
||||
const activeButton = useColorModeValue(
|
||||
{ bg: 'gray.200' },
|
||||
{ bg: 'whiteAlpha.200' }
|
||||
);
|
||||
return (
|
||||
<Flex
|
||||
zIndex="100"
|
||||
w={{ sm: '100%', md: 'auto' }}
|
||||
alignItems="center"
|
||||
flexDirection="row"
|
||||
bg={menuBg}
|
||||
flexWrap={'unset'}
|
||||
p="10px"
|
||||
pl="16px"
|
||||
borderRadius="30px"
|
||||
boxShadow={shadow}
|
||||
>
|
||||
<SidebarResponsive routes={routes} />
|
||||
|
||||
<Menu>
|
||||
<MenuButton p="0px" me="10px">
|
||||
<Icon
|
||||
mt="-5px"
|
||||
as={MdHelpOutline}
|
||||
color={navbarIcon}
|
||||
w="18px"
|
||||
h="18px"
|
||||
/>
|
||||
</MenuButton>
|
||||
<MenuList
|
||||
boxShadow={shadow}
|
||||
p="20px"
|
||||
borderRadius="20px"
|
||||
bg={menuBg}
|
||||
border="none"
|
||||
mt="22px"
|
||||
minW={{ base: 'unset' }}
|
||||
maxW={{ base: '360px', md: 'unset' }}
|
||||
>
|
||||
{/* <Flex bgImage={navImage} borderRadius="16px" mb="28px" alt="" /> */}
|
||||
<Flex flexDirection="column" rowGap="10px">
|
||||
<Link w="100%" href="/pricing">
|
||||
<Button
|
||||
bg={buttonBg}
|
||||
border="1px solid"
|
||||
color={textColor}
|
||||
borderColor={useColorModeValue('gray.200', 'whiteAlpha.100')}
|
||||
fontSize="sm"
|
||||
borderRadius="45px"
|
||||
w="100%"
|
||||
minW="44px"
|
||||
h="44px"
|
||||
_placeholder={{ color: 'gray.500' }}
|
||||
_hover={hoverButton}
|
||||
_active={activeButton}
|
||||
_focus={activeButton}
|
||||
>
|
||||
Pricing Plans
|
||||
</Button>
|
||||
</Link>
|
||||
<Link w="100%" href="mailto:hello@horizon-ui.com">
|
||||
<Button
|
||||
bg={buttonBg}
|
||||
border="1px solid"
|
||||
color={textColor}
|
||||
borderColor={useColorModeValue('gray.200', 'whiteAlpha.100')}
|
||||
fontSize="sm"
|
||||
borderRadius="45px"
|
||||
w="100%"
|
||||
minW="44px"
|
||||
h="44px"
|
||||
_placeholder={{ color: 'gray.500' }}
|
||||
_hover={hoverButton}
|
||||
_active={activeButton}
|
||||
_focus={activeButton}
|
||||
>
|
||||
Help & Support
|
||||
</Button>
|
||||
</Link>
|
||||
<Link w="100%" href="/#faqs">
|
||||
<Button
|
||||
bg={buttonBg}
|
||||
border="1px solid"
|
||||
color={textColor}
|
||||
borderColor={useColorModeValue('gray.200', 'whiteAlpha.100')}
|
||||
fontSize="sm"
|
||||
borderRadius="45px"
|
||||
w="100%"
|
||||
minW="44px"
|
||||
h="44px"
|
||||
_placeholder={{ color: 'gray.500' }}
|
||||
_hover={hoverButton}
|
||||
_active={activeButton}
|
||||
_focus={activeButton}
|
||||
>
|
||||
FAQs & More
|
||||
</Button>
|
||||
</Link>
|
||||
</Flex>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
<form onSubmit={(e) => handleRequest(e, SignOut, router)}>
|
||||
<input type="hidden" name="pathName" value={usePathname()} />
|
||||
<Button
|
||||
type="submit"
|
||||
bg="transparent"
|
||||
p="0px"
|
||||
pt="3px"
|
||||
_hover={{ background: 'transparent' }}
|
||||
_active={{ background: 'transparent' }}
|
||||
_focus={{ background: 'transparent' }}
|
||||
>
|
||||
<Box
|
||||
cursor="pointer"
|
||||
_hover={{ bg: 'none' }}
|
||||
_focus={{ bg: 'none' }}
|
||||
color="red.400"
|
||||
borderRadius="8px"
|
||||
me="10px"
|
||||
>
|
||||
{/* <Text> {`${props.userDetails?.avatar_url}`}</Text> */}
|
||||
<Icon
|
||||
mt="-5px"
|
||||
as={MdOutlineLogout}
|
||||
color={navbarIcon}
|
||||
w="20px"
|
||||
h="20px"
|
||||
/>
|
||||
</Box>
|
||||
</Button>
|
||||
</form>
|
||||
|
||||
<Link w="100%" href="/dashboard/settings">
|
||||
<Avatar h="40px" w="40px" src={user.user_metadata.avatar_url} />
|
||||
</Link>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
978
boilerplate-chakra-pro-main/components/pricing/index.tsx
Normal file
978
boilerplate-chakra-pro-main/components/pricing/index.tsx
Normal file
@@ -0,0 +1,978 @@
|
||||
'use client';
|
||||
|
||||
// @ts-nocheck
|
||||
import { FooterWebsite } from '../footer/FooterWebsite';
|
||||
import Card from '@/components/card/Card';
|
||||
import Faq from '@/components/landing/faq';
|
||||
import InnerContent from '@/components/layout/innerContent';
|
||||
import NavbarFixed from '@/components/navbar/NavbarFixed';
|
||||
import { Database } from '@/types_db';
|
||||
import { getStripe } from '@/utils/stripe/client';
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Flex,
|
||||
Icon,
|
||||
Link,
|
||||
SimpleGrid,
|
||||
Text,
|
||||
useColorModeValue
|
||||
} from '@chakra-ui/react';
|
||||
import { User } from '@supabase/supabase-js';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
FaCcVisa,
|
||||
FaCcMastercard,
|
||||
FaCcPaypal,
|
||||
FaCcAmex,
|
||||
FaCcApplePay
|
||||
} from 'react-icons/fa';
|
||||
import {
|
||||
MdChevronRight,
|
||||
MdCheckCircle,
|
||||
MdAttachMoney,
|
||||
MdLock,
|
||||
MdOutlineDoDisturbOn
|
||||
} from 'react-icons/md';
|
||||
import { checkoutWithStripe } from '@/utils/stripe/server';
|
||||
import { getErrorRedirect } from '@/utils/helpers';
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
export default function Pricing({ user, products, subscription }: Props) {
|
||||
const router = useRouter();
|
||||
const currentPath = usePathname();
|
||||
const [priceIdLoading, setPriceIdLoading] = useState<string>();
|
||||
const handleCheckout = async (price: Price) => {
|
||||
setPriceIdLoading(price.id);
|
||||
|
||||
if (!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);
|
||||
};
|
||||
const [version, setVersion] = useState('monthly');
|
||||
const textColor = useColorModeValue('#120F43', 'white');
|
||||
const brandColor = useColorModeValue('brand.500', 'white');
|
||||
const brandColorPrice = useColorModeValue('brand.500', 'white');
|
||||
const card = useColorModeValue('#fff', 'rgba(255, 255, 255, 0.05)');
|
||||
|
||||
return (
|
||||
<Flex
|
||||
id="pricing"
|
||||
bgSize="cover"
|
||||
w="100%"
|
||||
direction={{ base: 'column' }}
|
||||
pt={{ base: '120px', md: '180px' }}
|
||||
overflow="hidden"
|
||||
position="relative"
|
||||
>
|
||||
<NavbarFixed />
|
||||
<InnerContent
|
||||
zIndex="1"
|
||||
maxW={{ base: '100%', md: '100%', xl: '1170px' }}
|
||||
pb={{ base: '60px', md: '100px' }}
|
||||
>
|
||||
{/* Title */}
|
||||
<Flex
|
||||
px={{ base: '20px', md: '0px' }}
|
||||
w="100%"
|
||||
mb="40px"
|
||||
direction={{ base: 'column' }}
|
||||
>
|
||||
<Flex
|
||||
direction="column"
|
||||
textAlign="start"
|
||||
px={{ base: '20px', md: '40px', xl: '0px' }}
|
||||
mb={{ base: '0px', lg: '30px' }}
|
||||
justify="center"
|
||||
align="center"
|
||||
>
|
||||
<Text
|
||||
as="h3"
|
||||
textAlign={{ base: 'center', lg: 'center' }}
|
||||
fontWeight="700"
|
||||
letterSpacing="2px"
|
||||
color={brandColorPrice}
|
||||
fontSize={{ base: 'xs', md: 'md' }}
|
||||
w="100%"
|
||||
mb="10px"
|
||||
>
|
||||
PRICING PLANS
|
||||
</Text>
|
||||
<Text
|
||||
as="h2"
|
||||
textAlign={{ base: 'center', lg: 'center' }}
|
||||
color={textColor}
|
||||
fontWeight="800"
|
||||
fontSize={{ base: '28px', md: '40px', lg: '40px', xl: '48px' }}
|
||||
lineHeight={{ base: '38px', md: '50px', lg: '50px', xl: '60px' }}
|
||||
w={{ base: '100%', md: '90%', lg: '80%', xl: '74%' }}
|
||||
>
|
||||
Create outstanding Essays for less than a Netflix subscription per
|
||||
month
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<SimpleGrid
|
||||
px={{ base: '20px', md: '0px' }}
|
||||
w="100%"
|
||||
columns={{ base: 1, lg: 3 }}
|
||||
gap="20px"
|
||||
mb="20px"
|
||||
>
|
||||
<Card
|
||||
borderRadius="16px"
|
||||
bg={card}
|
||||
maxW="377px"
|
||||
mx="auto"
|
||||
alignItems={{ base: 'start', lg: 'center' }}
|
||||
p={{ base: '16px', md: '26px' }}
|
||||
pt={{ base: '26px', md: '36px' }}
|
||||
flexDirection="column"
|
||||
>
|
||||
<Flex mb="20px" mx="auto">
|
||||
<Text
|
||||
fontSize="22px"
|
||||
letterSpacing="0px"
|
||||
textAlign="center"
|
||||
fontWeight="700"
|
||||
color={textColor}
|
||||
>
|
||||
Demo Plan
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
zIndex="1"
|
||||
px={{ base: '18px', md: '34px' }}
|
||||
pt="40px"
|
||||
pb="40px"
|
||||
borderRadius="16px"
|
||||
bg="#F3F5FA"
|
||||
mb="40px"
|
||||
direction="column"
|
||||
w="100%"
|
||||
>
|
||||
<Flex mb="30px" direction="row" justify="center">
|
||||
<Text
|
||||
color="#120F43"
|
||||
fontSize={{ base: '48px', md: '54px' }}
|
||||
lineHeight="100%"
|
||||
letterSpacing="0px"
|
||||
fontWeight="extrabold"
|
||||
>
|
||||
Free
|
||||
</Text>
|
||||
</Flex>
|
||||
<Link href="/dashboard/signin">
|
||||
<Button
|
||||
py="20px"
|
||||
px="16px"
|
||||
fontSize="sm"
|
||||
variant="checkout"
|
||||
mx="auto"
|
||||
borderRadius="45px"
|
||||
w="100%"
|
||||
h="54px"
|
||||
>
|
||||
{subscription ? 'Manage' : 'Get started now'}
|
||||
<Icon as={MdChevronRight} mt="2px" h="16px" w="16px" />
|
||||
</Button>
|
||||
</Link>
|
||||
</Flex>
|
||||
|
||||
<Flex w={{ base: '100%', xl: '100%' }} direction="column">
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={brandColor}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Standard Essays
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={brandColor}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Up to 4 Essay types
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={brandColor}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Up to 300 words per Essay
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color="gray.400"
|
||||
as={MdOutlineDoDisturbOn}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color="gray.400"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Essay Tones (Academic, etc.)
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color="gray.400"
|
||||
as={MdOutlineDoDisturbOn}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color="gray.400"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Academic Citation formats (APA, etc)
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color="gray.400"
|
||||
as={MdOutlineDoDisturbOn}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color="gray.400"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Academic Levels (Master, etc.)
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color="gray.400"
|
||||
as={MdOutlineDoDisturbOn}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color="gray.400"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Premium features
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color="gray.400"
|
||||
as={MdOutlineDoDisturbOn}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color="gray.400"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Priority Support
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Card>
|
||||
{products.map((product) => {
|
||||
const price = product?.prices?.find(
|
||||
(price) => price.id === 'price_1P3gGXGx8VbJPRgzdEZODy8K'
|
||||
);
|
||||
if (product.id === 'prod_PtTCPDFZbburMa') {
|
||||
if (!price) return null;
|
||||
return (
|
||||
<Card
|
||||
key={product.id}
|
||||
borderRadius="16px"
|
||||
bg="#120F43"
|
||||
maxW="377px"
|
||||
mx="auto"
|
||||
alignItems={{ base: 'start', lg: 'center' }}
|
||||
p={{ base: '16px', md: '26px' }}
|
||||
pt={{ base: '26px', md: '36px' }}
|
||||
flexDirection="column"
|
||||
>
|
||||
{/* @ts-ignore-nextline */}
|
||||
<Flex mb="20px" mx="auto">
|
||||
<Text
|
||||
fontSize="22px"
|
||||
letterSpacing="0px"
|
||||
textAlign="center"
|
||||
fontWeight="700"
|
||||
color="white"
|
||||
>
|
||||
{product.name?.toString()}
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
zIndex="1"
|
||||
px={{ base: '18px', md: '34px' }}
|
||||
pt="40px"
|
||||
pb="40px"
|
||||
borderRadius="16px"
|
||||
bg="#120F43"
|
||||
boxShadow="0px 8px 25px -4px rgba(255, 255, 255, 0.30) inset"
|
||||
mb="40px"
|
||||
direction="column"
|
||||
w="100%"
|
||||
>
|
||||
<Flex mb="30px" direction="row" justify="center">
|
||||
<Text
|
||||
color="white"
|
||||
fontSize={{ base: '48px', md: '54px' }}
|
||||
lineHeight="100%"
|
||||
letterSpacing="0px"
|
||||
fontWeight="extrabold"
|
||||
>
|
||||
$
|
||||
{price.unit_amount !== null
|
||||
? price.unit_amount / 100
|
||||
: price.unit_amount}
|
||||
</Text>
|
||||
<Flex direction="column">
|
||||
<Text
|
||||
color="white"
|
||||
letterSpacing="0px"
|
||||
fontWeight="700"
|
||||
fontSize="sm"
|
||||
mt="auto"
|
||||
>
|
||||
{version !== 'yearly' ? '/month' : '/month'}
|
||||
</Text>
|
||||
{/* <Text
|
||||
color="white"
|
||||
letterSpacing="0px"
|
||||
fontWeight="500"
|
||||
fontSize="sm"
|
||||
textDecoration="line-through"
|
||||
>
|
||||
{version !== 'yearly' ? 'reg. $24' : 'reg. $24'}
|
||||
</Text> */}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Button
|
||||
py="20px"
|
||||
px="16px"
|
||||
fontSize="sm"
|
||||
variant="checkoutDark"
|
||||
mx="auto"
|
||||
borderRadius="45px"
|
||||
w="100%"
|
||||
h="54px"
|
||||
onClick={() => handleCheckout(price)}
|
||||
>
|
||||
{subscription ? 'Manage' : 'Get started now'}
|
||||
<Icon as={MdChevronRight} mt="2px" h="16px" w="16px" />
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
{/* Features */}
|
||||
<Flex w={{ base: '100%', xl: '100%' }} direction="column">
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color="white"
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color="white"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Unlimited Premium Essays / month
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color="white"
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color="white"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Access to 12+ Essay types
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color="white"
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color="white"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Up to 1500 words per Essay
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color="white"
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color="white"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Academic Citation formats (APA, etc)
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color="white"
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color="white"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Essay Tones (Academic, etc.)
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color="white"
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color="white"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Academic Levels (Master, etc.)
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color="white"
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color="white"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Exceptional Essays in seconds
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color="white"
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color="white"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Easy-to-use Essay Generator
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color="white"
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color="white"
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Priority Support
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
})}
|
||||
{products.map((product) => {
|
||||
const price = product?.prices?.find(
|
||||
(price) => price.id === 'price_1P3gMyGx8VbJPRgzkoB6Fp8F'
|
||||
);
|
||||
if (product.id === 'prod_PtTJ6R3RnzmIPX') {
|
||||
if (!price) return null;
|
||||
return (
|
||||
<Card
|
||||
key={product.id}
|
||||
borderRadius="16px"
|
||||
bg={card}
|
||||
maxW="377px"
|
||||
mx="auto"
|
||||
alignItems={{ base: 'start', lg: 'center' }}
|
||||
p={{ base: '16px', md: '26px' }}
|
||||
pt={{ base: '26px', md: '36px' }}
|
||||
flexDirection="column"
|
||||
>
|
||||
<Flex mb="20px" mx="auto">
|
||||
<Text
|
||||
fontSize="22px"
|
||||
letterSpacing="0px"
|
||||
textAlign="center"
|
||||
fontWeight="700"
|
||||
color={textColor}
|
||||
>
|
||||
{product.name?.toString()}
|
||||
</Text>
|
||||
<Badge
|
||||
display="flex"
|
||||
bg="#F2EFFF"
|
||||
alignItems="center"
|
||||
borderRadius="99px"
|
||||
px="6px"
|
||||
pb="8px"
|
||||
ms="10px"
|
||||
textColor={brandColor}
|
||||
>
|
||||
Save 35%
|
||||
</Badge>
|
||||
</Flex>
|
||||
<Flex
|
||||
zIndex="1"
|
||||
px={{ base: '18px', md: '34px' }}
|
||||
pt="40px"
|
||||
pb="40px"
|
||||
borderRadius="16px"
|
||||
bg="#F3F5FA"
|
||||
mb="40px"
|
||||
direction="column"
|
||||
w="100%"
|
||||
>
|
||||
<Flex mb="30px" direction="row" justify="center">
|
||||
<Text
|
||||
color={'transparent'}
|
||||
fontSize={{ base: '48px', md: '54px' }}
|
||||
lineHeight="100%"
|
||||
letterSpacing="0px"
|
||||
fontWeight="extrabold"
|
||||
bgGradient="linear-gradient(91deg, #3D1DFF 0%, #6147FF 22.40%, #D451FF 46.35%, #EC458D 75.00%, #FFCA8B 100%)"
|
||||
bgClip="text"
|
||||
>
|
||||
$
|
||||
{price.unit_amount !== null
|
||||
? price.unit_amount / 100
|
||||
: price.unit_amount}
|
||||
</Text>
|
||||
<Flex direction="column" ms="8px">
|
||||
<Text
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
fontWeight="700"
|
||||
fontSize="sm"
|
||||
mt="auto"
|
||||
>
|
||||
{version !== 'yearly' ? '/year' : '/year'}
|
||||
</Text>
|
||||
<Text
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
fontWeight="500"
|
||||
textDecoration="line-through"
|
||||
fontSize="sm"
|
||||
mt="2px"
|
||||
>
|
||||
{version !== 'yearly' ? 'reg.$108' : 'reg.$108'}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Button
|
||||
py="20px"
|
||||
px="16px"
|
||||
fontSize="sm"
|
||||
variant="checkout"
|
||||
mx="auto"
|
||||
borderRadius="45px"
|
||||
w="100%"
|
||||
h="54px"
|
||||
onClick={() => handleCheckout(price)}
|
||||
>
|
||||
{subscription ? 'Manage' : 'Get started now'}
|
||||
<Icon as={MdChevronRight} mt="2px" h="16px" w="16px" />
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
{/* Features */}
|
||||
<Flex w={{ base: '100%', xl: '100%' }} direction="column">
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={brandColor}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Unlimited Premium Essays / year
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={brandColor}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Access to 12+ Essay types
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={brandColor}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Up to 1500 words per Essay
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={brandColor}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Academic Citation formats (APA, etc)
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={brandColor}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Essay Tones (Academic, etc.)
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={brandColor}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
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={brandColor}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Exceptional Essays in seconds
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={brandColor}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Easy-to-use Essay Generator
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={brandColor}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize={{ base: 'sm', lg: 'sm', xl: 'md' }}
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Priority Support
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</SimpleGrid>
|
||||
<Flex
|
||||
px={{ base: '20px', md: '0px' }}
|
||||
direction="column"
|
||||
alignItems="center"
|
||||
mt="30px"
|
||||
>
|
||||
<Flex alignItems="center" mb="20px">
|
||||
<Icon
|
||||
me="4px"
|
||||
w="24px"
|
||||
h="24px"
|
||||
color={textColor}
|
||||
as={MdAttachMoney}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize="sm"
|
||||
fontWeight="500"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
7-Days money back
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="20px">
|
||||
<Icon me="4px" w="24px" h="24px" color={textColor} as={MdLock} />
|
||||
<Text
|
||||
as="span"
|
||||
fontSize="sm"
|
||||
fontWeight="500"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Secured AES-256 Encrypted payments powered by Stripe:
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="20px">
|
||||
<Icon me="10px" w="30px" h="30px" color={textColor} as={FaCcVisa} />
|
||||
<Icon
|
||||
me="10px"
|
||||
w="30px"
|
||||
h="30px"
|
||||
color={textColor}
|
||||
as={FaCcMastercard}
|
||||
/>
|
||||
<Icon
|
||||
me="10px"
|
||||
w="30px"
|
||||
h="30px"
|
||||
color={textColor}
|
||||
as={FaCcPaypal}
|
||||
/>
|
||||
<Icon
|
||||
me="10px"
|
||||
w="30px"
|
||||
h="30px"
|
||||
color={textColor}
|
||||
as={FaCcApplePay}
|
||||
/>
|
||||
<Icon w="30px" h="30px" color={textColor} as={FaCcAmex} />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</InnerContent>
|
||||
<Faq />
|
||||
<FooterWebsite />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
13
boilerplate-chakra-pro-main/components/providers.tsx
Normal file
13
boilerplate-chakra-pro-main/components/providers.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
'use client';
|
||||
|
||||
import theme from '@/theme/theme';
|
||||
import { CacheProvider } from '@chakra-ui/next-js';
|
||||
import { ChakraProvider } from '@chakra-ui/react';
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<CacheProvider>
|
||||
<ChakraProvider theme={theme}>{children}</ChakraProvider>
|
||||
</CacheProvider>
|
||||
);
|
||||
}
|
||||
88
boilerplate-chakra-pro-main/components/ratings/index.tsx
Normal file
88
boilerplate-chakra-pro-main/components/ratings/index.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
import { IoIosStar } from 'react-icons/io';
|
||||
import { Flex, Icon, Text, useColorModeValue } from '@chakra-ui/react';
|
||||
|
||||
export default function Ratings(props: {
|
||||
dark?: boolean;
|
||||
stats: string;
|
||||
stars?: 1 | 2 | 3 | 4 | 5;
|
||||
rating: string | number;
|
||||
}) {
|
||||
const { rating, stats, stars } = props;
|
||||
const gray = useColorModeValue('whiteAlpha.300', 'gray.200');
|
||||
const textColor = useColorModeValue('navy.900', 'white');
|
||||
return (
|
||||
<Flex direction="column" align="center" justify="center">
|
||||
<Text color={gray} mt={{base:"12px", lg:"40px"}} mb="10px" justifySelf={'center'} fontWeight='500'
|
||||
>
|
||||
{stats}
|
||||
</Text>
|
||||
{stars === 1 ? (
|
||||
<Flex align="center" justify={{ base: 'center', md: 'flex-start' }}>
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color={gray} />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color={gray} />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color={gray} />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" me="8px" color={gray} />
|
||||
<Text fontWeight="bold" my="2px" fontSize="lg" color={textColor}>
|
||||
{rating}
|
||||
</Text>
|
||||
</Flex>
|
||||
) : stars === 2 ? (
|
||||
<Flex align="center" justify={{ base: 'center', md: 'flex-start' }}>
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color={gray} />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color={gray} />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" me="8px" color={gray} />
|
||||
<Text fontWeight="bold" my="2px" fontSize="lg" color={textColor}>
|
||||
{rating}
|
||||
</Text>
|
||||
</Flex>
|
||||
) : stars === 3 ? (
|
||||
<Flex align="center" justify={{ base: 'center', md: 'flex-start' }}>
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color={gray} />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" me="8px" color={gray} />
|
||||
<Text fontWeight="bold" my="2px" fontSize="lg" color={textColor}>
|
||||
{rating}
|
||||
</Text>
|
||||
</Flex>
|
||||
) : stars === 4 ? (
|
||||
<Flex align="center" justify={{ base: 'center', md: 'flex-start' }}>
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" me="8px" color={gray} />
|
||||
<Text fontWeight="bold" my="2px" fontSize="lg" color={textColor}>
|
||||
{rating}
|
||||
</Text>
|
||||
</Flex>
|
||||
) : stars === 5 ? (
|
||||
<Flex align="center" justify={{ base: 'center', md: 'flex-start' }}>
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" me="8px" color="#F6AD55" />
|
||||
<Text fontWeight="bold" my="2px" fontSize="lg" color={textColor}>
|
||||
{rating}
|
||||
</Text>
|
||||
</Flex>
|
||||
) : (
|
||||
<Flex align="center" justify={{ base: 'center', md: 'flex-start' }}>
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" color="#F6AD55" />
|
||||
<Icon as={IoIosStar} h="22px" w="22px" me="8px" color="#F6AD55" />
|
||||
<Text fontWeight="bold" my="2px" fontSize="lg" color={textColor}>
|
||||
{rating}
|
||||
</Text>
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
92
boilerplate-chakra-pro-main/components/routes.tsx
Normal file
92
boilerplate-chakra-pro-main/components/routes.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
// Auth Imports
|
||||
import { IRoute } from '@/types/types';
|
||||
import { Icon } from '@chakra-ui/react';
|
||||
import {
|
||||
MdCreditCard,
|
||||
MdHome,
|
||||
MdOutlineManageAccounts,
|
||||
MdWorkspacePremium,
|
||||
} from 'react-icons/md';
|
||||
|
||||
export const routes: IRoute[] = [
|
||||
{
|
||||
name: 'Main Dashboard',
|
||||
path: '/dashboard/main',
|
||||
icon: (
|
||||
<Icon as={MdHome} mt="-7px" width="20px" height="20px" color="inherit" />
|
||||
),
|
||||
collapse: false,
|
||||
},
|
||||
{
|
||||
name: 'AI Pages',
|
||||
path: '/ai-pages',
|
||||
icon: (
|
||||
<Icon
|
||||
as={MdWorkspacePremium}
|
||||
mt="-7px"
|
||||
width="20px"
|
||||
height="20px"
|
||||
color="inherit"
|
||||
/>
|
||||
),
|
||||
collapse: true,
|
||||
items: [
|
||||
{
|
||||
name: 'AI Generator',
|
||||
path: '/dashboard/ai-generator',
|
||||
collapse: false,
|
||||
},
|
||||
{
|
||||
name: 'AI Assistant',
|
||||
path: '/dashboard/ai-assistant',
|
||||
collapse: false,
|
||||
},
|
||||
{
|
||||
name: 'AI Chat',
|
||||
path: '/dashboard/ai-chat',
|
||||
collapse: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Users List',
|
||||
path: '/dashboard/users-list',
|
||||
icon: (
|
||||
<Icon height="24px" viewBox="0 -960 960 960" width="24px" mt="-7px">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M640-400q-50 0-85-35t-35-85q0-50 35-85t85-35q50 0 85 35t35 85q0 50-35 85t-85 35ZM400-160v-76q0-21 10-40t28-30q45-27 95.5-40.5T640-360q56 0 106.5 13.5T842-306q18 11 28 30t10 40v76H400Zm86-80h308q-35-20-74-30t-80-10q-41 0-80 10t-74 30Zm154-240q17 0 28.5-11.5T680-520q0-17-11.5-28.5T640-560q-17 0-28.5 11.5T600-520q0 17 11.5 28.5T640-480Zm0-40Zm0 280ZM120-400v-80h320v80H120Zm0-320v-80h480v80H120Zm324 160H120v-80h360q-14 17-22.5 37T444-560Z"
|
||||
></path>
|
||||
</Icon>
|
||||
),
|
||||
collapse: false,
|
||||
},
|
||||
{
|
||||
name: 'Profile Settings',
|
||||
path: '/dashboard/settings',
|
||||
icon: (
|
||||
<Icon
|
||||
mt="-7px"
|
||||
as={MdOutlineManageAccounts}
|
||||
width="20px"
|
||||
height="20px"
|
||||
color="inherit"
|
||||
/>
|
||||
),
|
||||
collapse: false,
|
||||
},
|
||||
{
|
||||
name: 'Subscription',
|
||||
path: '/dashboard/subscription',
|
||||
icon: (
|
||||
<Icon
|
||||
mt="-7px"
|
||||
as={MdCreditCard}
|
||||
width="20px"
|
||||
height="20px"
|
||||
color="inherit"
|
||||
/>
|
||||
),
|
||||
collapse: false,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,42 @@
|
||||
'use client';
|
||||
|
||||
import { Box } from '@chakra-ui/react';
|
||||
|
||||
export const renderTrack = ({ style, ...props }: any) => {
|
||||
const trackStyle = {
|
||||
position: 'absolute',
|
||||
maxWidth: '100%',
|
||||
transition: 'opacity 200ms ease 0s',
|
||||
opacity: 0,
|
||||
background: 'transparent',
|
||||
bottom: 2,
|
||||
top: 2,
|
||||
borderRadius: 3,
|
||||
right: 0,
|
||||
};
|
||||
return <div style={{ ...style, ...trackStyle }} {...props} />;
|
||||
};
|
||||
export const renderThumb = ({ style, ...props }: any) => {
|
||||
const thumbStyle = {
|
||||
borderRadius: 15,
|
||||
background: 'rgba(222, 222, 222, .1)',
|
||||
};
|
||||
return <div style={{ ...style, ...thumbStyle }} {...props} />;
|
||||
};
|
||||
export const renderView = ({ style, ...props }: any) => {
|
||||
const viewStyle = {
|
||||
width: '100%',
|
||||
marginBottom: -22,
|
||||
|
||||
};
|
||||
return (
|
||||
<Box
|
||||
me={{ base: '0px !important', '2xl': '-16px !important' }}
|
||||
transform="translate(0px, 0px)"
|
||||
boxSizing={'unset'}
|
||||
pe="15px"
|
||||
style={{ ...style, ...viewStyle }}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
'use client';
|
||||
import { Flex, useColorModeValue } from '@chakra-ui/react';
|
||||
|
||||
const HSeparator = (props: { variant?: string; [x: string]: any }) => {
|
||||
const textColor = useColorModeValue('gray.200', 'whiteAlpha.300');
|
||||
const { variant, ...rest } = props;
|
||||
return <Flex h="1px" w="100%" bg={textColor} {...rest} />;
|
||||
};
|
||||
|
||||
const VSeparator = (props: { variant?: string; [x: string]: any }) => {
|
||||
const textColor = useColorModeValue('gray.200', 'whiteAlpha.300');
|
||||
const { variant, ...rest } = props;
|
||||
return <Flex w="1px" bg={textColor} {...rest} />;
|
||||
};
|
||||
|
||||
export { HSeparator, VSeparator };
|
||||
144
boilerplate-chakra-pro-main/components/sidebar/Sidebar.tsx
Normal file
144
boilerplate-chakra-pro-main/components/sidebar/Sidebar.tsx
Normal file
@@ -0,0 +1,144 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
renderThumb,
|
||||
renderTrack,
|
||||
renderView
|
||||
} from '@/components/scrollbar/Scrollbar';
|
||||
import Content from '@/components/sidebar/components/Content';
|
||||
import { ApiKeyContext } from '@/contexts/layout';
|
||||
import { IRoute } from '@/types/types';
|
||||
import { isWindowAvailable } from '@/utils/navigation';
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Drawer,
|
||||
DrawerBody,
|
||||
Icon,
|
||||
useColorModeValue,
|
||||
DrawerOverlay,
|
||||
useDisclosure,
|
||||
DrawerContent,
|
||||
DrawerCloseButton
|
||||
} from '@chakra-ui/react';
|
||||
import React, { PropsWithChildren, useContext } from 'react';
|
||||
import { Scrollbars } from 'react-custom-scrollbars-2';
|
||||
import { IoMenuOutline } from 'react-icons/io5';
|
||||
|
||||
export interface SidebarProps extends PropsWithChildren {
|
||||
routes: IRoute[];
|
||||
[x: string]: any;
|
||||
}
|
||||
|
||||
function Sidebar(props: SidebarProps) {
|
||||
const { routes } = props;
|
||||
const { apiKey, setApiKey } = useContext(ApiKeyContext);
|
||||
let variantChange = '0.2s linear';
|
||||
let shadow = useColorModeValue(
|
||||
'14px 17px 40px 4px rgba(112, 144, 176, 0.08)',
|
||||
'unset'
|
||||
);
|
||||
let sidebarBg = useColorModeValue('white', 'navy.800');
|
||||
let sidebarRadius = '14px';
|
||||
let sidebarMargins = '0px';
|
||||
return (
|
||||
<Box display={{ base: 'none', xl: 'block' }} position="fixed" minH="100%">
|
||||
<Box
|
||||
bg={sidebarBg}
|
||||
transition={variantChange}
|
||||
w="285px"
|
||||
ms={{
|
||||
sm: '16px'
|
||||
}}
|
||||
my={{
|
||||
sm: '16px'
|
||||
}}
|
||||
h="calc(100vh - 32px)"
|
||||
m={sidebarMargins}
|
||||
borderRadius={sidebarRadius}
|
||||
minH="100%"
|
||||
overflowX="hidden"
|
||||
boxShadow={shadow}
|
||||
>
|
||||
<Scrollbars
|
||||
autoHide
|
||||
renderTrackVertical={renderTrack}
|
||||
renderThumbVertical={renderThumb}
|
||||
renderView={renderView}
|
||||
universal={true}
|
||||
>
|
||||
<Content setApiKey={setApiKey} routes={routes} />
|
||||
</Scrollbars>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// -------------- Sidebar Function for Navbar burger --------------
|
||||
export function SidebarResponsive(props: SidebarProps) {
|
||||
let sidebarBackgroundColor = useColorModeValue('white', 'navy.800');
|
||||
let menuColor = useColorModeValue('gray.400', 'white');
|
||||
const { apiKey, setApiKey } = useContext(ApiKeyContext);
|
||||
// SIDEBAR
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
|
||||
const { routes } = props;
|
||||
return (
|
||||
<Flex display={{ sm: 'flex', xl: 'none' }} alignItems="center">
|
||||
<Flex w="max-content" h="max-content" onClick={onOpen}>
|
||||
<Icon
|
||||
as={IoMenuOutline}
|
||||
color={menuColor}
|
||||
my="auto"
|
||||
w="20px"
|
||||
h="20px"
|
||||
me="10px"
|
||||
_hover={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</Flex>
|
||||
<Drawer
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
placement={
|
||||
isWindowAvailable() && document.documentElement.dir === 'rtl'
|
||||
? 'right'
|
||||
: 'left'
|
||||
}
|
||||
>
|
||||
<DrawerOverlay />
|
||||
<DrawerContent
|
||||
w="285px"
|
||||
maxW="285px"
|
||||
ms={{
|
||||
sm: '16px'
|
||||
}}
|
||||
my={{
|
||||
sm: '16px'
|
||||
}}
|
||||
borderRadius="16px"
|
||||
bg={sidebarBackgroundColor}
|
||||
>
|
||||
<DrawerCloseButton
|
||||
zIndex="3"
|
||||
onClick={onClose}
|
||||
_focus={{ boxShadow: 'none' }}
|
||||
_hover={{ boxShadow: 'none' }}
|
||||
/>
|
||||
<DrawerBody maxW="285px" px="0rem" pb="0">
|
||||
<Scrollbars
|
||||
autoHide
|
||||
renderTrackVertical={renderTrack}
|
||||
renderThumbVertical={renderThumb}
|
||||
renderView={renderView}
|
||||
universal={true}
|
||||
>
|
||||
<Content setApiKey={setApiKey} routes={routes} />
|
||||
</Scrollbars>
|
||||
</DrawerBody>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default Sidebar;
|
||||
@@ -0,0 +1,38 @@
|
||||
'use client';
|
||||
|
||||
import logo from '/public/logo-horizon-boilerplate.png';
|
||||
import { HSeparator } from '@/components/separator/Separator';
|
||||
import { Flex, Image, Link, Text } from '@chakra-ui/react';
|
||||
|
||||
export function SidebarBrand() {
|
||||
return (
|
||||
<Flex alignItems="center" flexDirection="column" ps="24px">
|
||||
<Link
|
||||
display={'flex'}
|
||||
alignItems="center"
|
||||
justifyContent={'center'}
|
||||
href="/"
|
||||
mb="34px"
|
||||
>
|
||||
<Image
|
||||
alt=" "
|
||||
w="36px"
|
||||
src={logo.src}
|
||||
/>
|
||||
<Text
|
||||
ms="8px"
|
||||
me="10px"
|
||||
fontWeight="800"
|
||||
fontSize={'17px'}
|
||||
letterSpacing="-0.2px"
|
||||
color="#120F43"
|
||||
>
|
||||
Horizon UI Boilerplate
|
||||
</Text>
|
||||
</Link>
|
||||
<HSeparator mb="20px" w="310px" />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default SidebarBrand;
|
||||
@@ -0,0 +1,111 @@
|
||||
'use client';
|
||||
|
||||
// Custom components
|
||||
import { useSupabase } from '@/app/supabase-provider';
|
||||
import Brand from '@/components/sidebar/components/Brand';
|
||||
import Links from '@/components/sidebar/components/Links';
|
||||
import SidebarCard from '@/components/sidebar/components/SidebarCard';
|
||||
import { UserContext } from '@/contexts/layout';
|
||||
import { IRoute } from '@/types/types';
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
Icon,
|
||||
Stack,
|
||||
Text,
|
||||
useColorModeValue
|
||||
} from '@chakra-ui/react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { PropsWithChildren, useContext } from 'react';
|
||||
import { FiLogOut } from 'react-icons/fi';
|
||||
|
||||
// FUNCTIONS
|
||||
|
||||
interface SidebarContent extends PropsWithChildren {
|
||||
routes: IRoute[];
|
||||
[x: string]: any;
|
||||
}
|
||||
function SidebarContent(props: SidebarContent) {
|
||||
const router = useRouter();
|
||||
const { supabase } = useSupabase();
|
||||
const { routes, setApiKey } = props;
|
||||
const user = useContext(UserContext);
|
||||
console.log(user.user_metadata.avatar_url);
|
||||
const textColor = useColorModeValue('#120F43', 'white');
|
||||
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.300');
|
||||
const shadowPillBar = useColorModeValue(
|
||||
'4px 17px 40px 4px rgba(112, 144, 176, 0.08)',
|
||||
'none'
|
||||
);
|
||||
return (
|
||||
<Flex
|
||||
direction="column"
|
||||
height="100%"
|
||||
pt="36px"
|
||||
pb="26px"
|
||||
borderRadius="30px"
|
||||
maxW="285px"
|
||||
w="100%"
|
||||
>
|
||||
<Brand />
|
||||
<Stack direction="column" mb="auto" mt="8px" ps="20px" pe="16px">
|
||||
<Box ps="0px" pe={{ md: '0px', '2xl': '0px' }}>
|
||||
<Links routes={routes} />
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
<Box
|
||||
mt="60px"
|
||||
width={'100%'}
|
||||
display={'flex'}
|
||||
justifyContent={'center'}
|
||||
ps="20px"
|
||||
pe="20px"
|
||||
>
|
||||
<SidebarCard />
|
||||
</Box>
|
||||
<Flex
|
||||
mt="20px"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
boxShadow={shadowPillBar}
|
||||
borderRadius="30px"
|
||||
p="14px"
|
||||
px="34px"
|
||||
>
|
||||
<Avatar
|
||||
h="34px"
|
||||
w="34px"
|
||||
me="10px"
|
||||
src={user.user_metadata.avatar_url}
|
||||
/>
|
||||
<Text color={textColor} fontSize="sm" fontWeight="700" me="10px">
|
||||
{user.user_metadata.full_name ? user.user_metadata.full_name : 'User'}
|
||||
</Text>
|
||||
<Button
|
||||
ms="auto"
|
||||
variant="transparent"
|
||||
border="1px solid"
|
||||
borderColor={borderColor}
|
||||
borderRadius="full"
|
||||
w="34px"
|
||||
h="34px"
|
||||
px="0px"
|
||||
minW="34px"
|
||||
justifyContent={'center'}
|
||||
alignItems="center"
|
||||
onClick={(e) => {
|
||||
supabase.auth.signOut();
|
||||
router.push('/');
|
||||
}}
|
||||
>
|
||||
<Icon as={FiLogOut} width="16px" height="16px" color="inherit" />
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default SidebarContent;
|
||||
@@ -0,0 +1,823 @@
|
||||
'use client';
|
||||
|
||||
/* eslint-disable */
|
||||
import NavLink from '@/components/link/NavLink';
|
||||
import {
|
||||
PlanContext,
|
||||
ProductsContext,
|
||||
UserContext,
|
||||
UserDetailsContext
|
||||
} from '@/contexts/layout';
|
||||
import modalImage from '@/public/Modal.png';
|
||||
import { IRoute } from '@/types/types';
|
||||
import { Database } from '@/types_db';
|
||||
import { getRedirectMethod } from '@/utils/auth-helpers/settings';
|
||||
import { getErrorRedirect } from '@/utils/helpers';
|
||||
import { getStripe } from '@/utils/stripe/client';
|
||||
import { checkoutWithStripe } from '@/utils/stripe/server';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionButton,
|
||||
AccordionIcon,
|
||||
AccordionItem,
|
||||
AccordionPanel,
|
||||
Badge,
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
HStack,
|
||||
Icon,
|
||||
Image,
|
||||
List,
|
||||
ListItem,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalOverlay,
|
||||
Text,
|
||||
useColorModeValue,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { PropsWithChildren, useCallback, useContext, useState } from 'react';
|
||||
import { FaCircle } from 'react-icons/fa';
|
||||
import { IoIosStar, IoMdAdd } from 'react-icons/io';
|
||||
import { MdCheckCircle, MdChevronRight } from 'react-icons/md';
|
||||
|
||||
interface SidebarLinksProps extends PropsWithChildren {
|
||||
routes: IRoute[];
|
||||
[x: string]: any;
|
||||
}
|
||||
type Price = Database['public']['Tables']['prices']['Row'];
|
||||
|
||||
export function SidebarLinks(props: SidebarLinksProps) {
|
||||
const pathname = usePathname();
|
||||
const textColor = useColorModeValue('#120F43', 'white');
|
||||
let activeColor = useColorModeValue('#120F43', 'white');
|
||||
let inactiveColor = useColorModeValue('gray.500', 'gray.500');
|
||||
let borderColor = useColorModeValue('gray.200', 'whiteAlpha.300');
|
||||
let activeIcon = useColorModeValue('brand.500', 'white');
|
||||
let iconColor = useColorModeValue('#120F43', 'white');
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
// verifies if routeName is the one active (in browser input)
|
||||
const activeRoute = useCallback(
|
||||
(route: string | { [x: string]: any }) => {
|
||||
let foundActive = 0;
|
||||
if (typeof route === 'string') {
|
||||
return pathname?.includes(route);
|
||||
} else if (route?.items) {
|
||||
route.items.map((item: { [x: string]: any }) => {
|
||||
if (pathname?.includes(item.path)) foundActive = foundActive + 1;
|
||||
});
|
||||
}
|
||||
if (foundActive > 0) return true;
|
||||
else return false;
|
||||
},
|
||||
[pathname]
|
||||
);
|
||||
|
||||
const router = getRedirectMethod() === 'client' ? useRouter() : null;
|
||||
const { routes } = props;
|
||||
const user = useContext(UserContext);
|
||||
const [priceIdLoading, setPriceIdLoading] = useState<string>();
|
||||
const { plan, setPlan } = useContext(PlanContext);
|
||||
const products = useContext(ProductsContext);
|
||||
const currentPath = usePathname();
|
||||
|
||||
// this function creates the links and collapses that appear in the sidebar (left menu)
|
||||
const createLinks = (routes: IRoute[]) => {
|
||||
const handleCheckout = async (price: Price) => {
|
||||
setPriceIdLoading(price.id);
|
||||
|
||||
if (!user) {
|
||||
setPriceIdLoading(undefined);
|
||||
return router.push('/dashboard/signin/signup');
|
||||
}
|
||||
|
||||
const { errorRedirect, sessionId } = await checkoutWithStripe(
|
||||
price,
|
||||
currentPath
|
||||
);
|
||||
|
||||
if (errorRedirect) {
|
||||
setPriceIdLoading(undefined);
|
||||
return router.push(errorRedirect);
|
||||
}
|
||||
|
||||
if (!sessionId) {
|
||||
setPriceIdLoading(undefined);
|
||||
return router.push(
|
||||
getErrorRedirect(
|
||||
currentPath,
|
||||
'An unknown error occurred.',
|
||||
'Please try again later or contact a system administrator.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const stripe = await getStripe();
|
||||
stripe?.redirectToCheckout({ sessionId });
|
||||
|
||||
setPriceIdLoading(undefined);
|
||||
};
|
||||
return routes.map((route, key) => {
|
||||
if (route.collapse && !route.invisible) {
|
||||
return (
|
||||
<Accordion
|
||||
allowToggle
|
||||
defaultIndex={activeRoute(route) ? 0 : 1}
|
||||
key={key}
|
||||
>
|
||||
<AccordionItem border="none" mb="14px" key={key}>
|
||||
<AccordionButton
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
mb="4px"
|
||||
justifyContent="center"
|
||||
_hover={{
|
||||
bg: 'unset'
|
||||
}}
|
||||
_focus={{
|
||||
boxShadow: 'none'
|
||||
}}
|
||||
borderRadius="8px"
|
||||
w="100%"
|
||||
py="0px"
|
||||
ms={0}
|
||||
>
|
||||
{route.icon ? (
|
||||
<Flex align="center" justifyContent="space-between" w="100%">
|
||||
<HStack spacing={activeRoute(route) ? '22px' : '26px'}>
|
||||
<Flex
|
||||
w="100%"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Box
|
||||
color={
|
||||
activeRoute(route) ? activeIcon : inactiveColor
|
||||
}
|
||||
me="12px"
|
||||
mt="6px"
|
||||
>
|
||||
{route.icon}
|
||||
</Box>
|
||||
<Text
|
||||
me="auto"
|
||||
color={activeRoute(route) ? activeColor : 'gray.500'}
|
||||
fontWeight="500"
|
||||
letterSpacing="0px"
|
||||
fontSize="sm"
|
||||
>
|
||||
{route.name}
|
||||
</Text>
|
||||
</Flex>
|
||||
</HStack>
|
||||
<AccordionIcon ms="auto" color={'gray.500'} />
|
||||
</Flex>
|
||||
) : (
|
||||
<Flex pt="0px" pb="10px" alignItems="center" w="100%">
|
||||
<HStack
|
||||
spacing={
|
||||
activeRoute(route.path.toLowerCase()) ? '22px' : '26px'
|
||||
}
|
||||
ps="32px"
|
||||
>
|
||||
<Text
|
||||
me="auto"
|
||||
color={
|
||||
activeRoute(route.path.toLowerCase())
|
||||
? activeColor
|
||||
: inactiveColor
|
||||
}
|
||||
fontWeight="500"
|
||||
letterSpacing="0px"
|
||||
fontSize="sm"
|
||||
>
|
||||
{route.name}
|
||||
</Text>
|
||||
</HStack>
|
||||
<AccordionIcon ms="auto" color={'gray.500'} />
|
||||
</Flex>
|
||||
)}
|
||||
</AccordionButton>
|
||||
<AccordionPanel py="0px" ps={'8px'}>
|
||||
<List>
|
||||
{
|
||||
route.icon && route.items
|
||||
? createLinks(route.items) // for bullet accordion links
|
||||
: route.items
|
||||
? createAccordionLinks(route.items)
|
||||
: '' // for non-bullet accordion links
|
||||
}
|
||||
</List>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
);
|
||||
} else if (!route.invisible) {
|
||||
return (
|
||||
<Box key={key}>
|
||||
{route.icon ? (
|
||||
<Flex
|
||||
align="center"
|
||||
justifyContent="space-between"
|
||||
w="100%"
|
||||
maxW="100%"
|
||||
ps="17px"
|
||||
mb="0px"
|
||||
>
|
||||
<HStack
|
||||
w="100%"
|
||||
mb="14px"
|
||||
spacing={
|
||||
activeRoute(route.path.toLowerCase()) ? '22px' : '26px'
|
||||
}
|
||||
>
|
||||
{route.path.includes('premium') && !props.subscription ? (
|
||||
<Flex w="100%">
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<ModalOverlay bg="rgba(0, 0, 0, 0.85)" />
|
||||
<ModalContent
|
||||
mx="8px"
|
||||
bg="transparent"
|
||||
boxShadow="unset"
|
||||
maxW="unset"
|
||||
w="unset"
|
||||
>
|
||||
<ModalBody p="0px" position={'relative'}>
|
||||
<Flex>
|
||||
<Image
|
||||
display={{ base: 'none', md: 'block' }}
|
||||
zIndex="98"
|
||||
borderLeftRadius="16px"
|
||||
src={modalImage.src}
|
||||
w="340px"
|
||||
/>
|
||||
<Flex
|
||||
bg="white"
|
||||
borderLeftRadius={{ base: '16px', md: '0px' }}
|
||||
borderRightRadius="16px"
|
||||
direction={'column'}
|
||||
px={{ base: '30px', md: '42px' }}
|
||||
py="34px"
|
||||
w={{ md: '412px', lg: '456px' }}
|
||||
minW={{ md: '412px', lg: '456px' }}
|
||||
>
|
||||
<Text
|
||||
fontSize="26px"
|
||||
fontWeight={'800'}
|
||||
color={textColor}
|
||||
mb="12px"
|
||||
>
|
||||
Upgrade to Unlimited
|
||||
</Text>
|
||||
<Text
|
||||
mb="24px"
|
||||
fontWeight="500"
|
||||
fontSize="md"
|
||||
color="gray.500"
|
||||
>
|
||||
Get access to all features and generate
|
||||
premium and exclusive essays with our
|
||||
unlimited plan!
|
||||
</Text>
|
||||
{/* Features */}
|
||||
<Flex
|
||||
w={{ base: '100%', xl: '80%' }}
|
||||
direction="column"
|
||||
>
|
||||
<Flex alignItems="center" mb="20px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={'green.500'}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize="sm"
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Access to 12+ Essay types
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="20px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={'green.500'}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize="sm"
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Up to 1500 words per Essay
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="20px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={'green.500'}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize="sm"
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Academic Citation formats (APA, etc.)
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="20px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={'green.500'}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize="sm"
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Academic Levels (Master, etc.)
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={'green.500'}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize="sm"
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Essay Tones (Academic, etc.)
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
{/* YEARLY */}
|
||||
<Flex
|
||||
onClick={() =>
|
||||
setPlan({
|
||||
product: 'prod_PtTJ6R3RnzmIPX',
|
||||
price: 'price_1P3gMyGx8VbJPRgzkoB6Fp8F'
|
||||
})
|
||||
}
|
||||
transition="0.15s linear"
|
||||
align="center"
|
||||
position="relative"
|
||||
border="1px solid"
|
||||
borderColor={
|
||||
plan.product === 'prod_PtTJ6R3RnzmIPX'
|
||||
? 'brand.500'
|
||||
: borderColor
|
||||
}
|
||||
borderRadius="10px"
|
||||
w="100%"
|
||||
py="14px"
|
||||
px="14px"
|
||||
cursor="pointer"
|
||||
mb="20px"
|
||||
>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
fontWeight={'700'}
|
||||
color="#120F43"
|
||||
mb="2x"
|
||||
ms="8px"
|
||||
me="8px"
|
||||
>
|
||||
Yearly
|
||||
</Text>
|
||||
<Badge
|
||||
display={{
|
||||
base: 'flex',
|
||||
lg: 'none',
|
||||
xl: 'flex'
|
||||
}}
|
||||
colorScheme="green"
|
||||
borderRadius="4px"
|
||||
color="green.500"
|
||||
textTransform={'none'}
|
||||
letterSpacing="0px"
|
||||
px="0px"
|
||||
w="max-content"
|
||||
>
|
||||
Save 35%
|
||||
</Badge>
|
||||
<Text
|
||||
display="flex"
|
||||
ms="auto"
|
||||
fontSize="md"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
fontWeight="600"
|
||||
lineHeight="100%"
|
||||
>
|
||||
$5.75
|
||||
<Text
|
||||
fontSize={'14px'}
|
||||
color="gray.500"
|
||||
fontWeight="500"
|
||||
ms="4px"
|
||||
as="span"
|
||||
>
|
||||
/month
|
||||
</Text>
|
||||
</Text>
|
||||
</Flex>
|
||||
{/* END YEARLY */}
|
||||
{/* MONTHLY */}
|
||||
<Flex
|
||||
onClick={() =>
|
||||
setPlan({
|
||||
product: 'prod_PtTCPDFZbburMa',
|
||||
price: 'price_1P3gGXGx8VbJPRgzdEZODy8K'
|
||||
})
|
||||
}
|
||||
transition="0.15s linear"
|
||||
align="center"
|
||||
position="relative"
|
||||
border="1px solid"
|
||||
borderColor={
|
||||
plan.product === 'prod_PtTCPDFZbburMa'
|
||||
? 'brand.500'
|
||||
: borderColor
|
||||
}
|
||||
borderRadius="10px"
|
||||
w="100%"
|
||||
py="16px"
|
||||
px="14px"
|
||||
cursor="pointer"
|
||||
mb="28px"
|
||||
>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
fontWeight={'700'}
|
||||
color="#120F43"
|
||||
mb="2x"
|
||||
ms="8px"
|
||||
me="4px"
|
||||
>
|
||||
Monthly
|
||||
</Text>
|
||||
<Text
|
||||
display="flex"
|
||||
ms="auto"
|
||||
fontSize="md"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
fontWeight="600"
|
||||
lineHeight="100%"
|
||||
>
|
||||
$9
|
||||
<Text
|
||||
fontSize={'14px'}
|
||||
color="gray.500"
|
||||
fontWeight="500"
|
||||
ms="4px"
|
||||
as="span"
|
||||
>
|
||||
/month
|
||||
</Text>
|
||||
</Text>
|
||||
</Flex>
|
||||
{/* END MONTHLY */}
|
||||
{products.map((product: any) => {
|
||||
const price = product?.prices?.find(
|
||||
(price: any) => price.id === plan.price
|
||||
);
|
||||
if (product.id === plan.product) {
|
||||
if (!price) return null;
|
||||
return (
|
||||
<Button
|
||||
py="20px"
|
||||
px="16px"
|
||||
fontSize="sm"
|
||||
variant="primary"
|
||||
borderRadius="45px"
|
||||
w={{ base: '100%' }}
|
||||
h="54px"
|
||||
mb="28px"
|
||||
_hover={{
|
||||
boxShadow:
|
||||
'0px 21px 27px -10px rgba(96, 60, 255, 0.48) !important',
|
||||
bg:
|
||||
'linear-gradient(15.46deg, #4A25E1 26.3%, #7B5AFF 86.4%) !important',
|
||||
_disabled: {
|
||||
bg:
|
||||
'linear-gradient(15.46deg, #4A25E1 26.3%, #7B5AFF 86.4%)'
|
||||
}
|
||||
}}
|
||||
onClick={() => handleCheckout(price)}
|
||||
>
|
||||
Upgrade now
|
||||
<Icon
|
||||
as={MdChevronRight}
|
||||
mt="2px"
|
||||
h="16px"
|
||||
w="16px"
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
})}
|
||||
<Text
|
||||
fontSize="xs"
|
||||
color="gray.500"
|
||||
fontWeight={'500'}
|
||||
mx="auto"
|
||||
mb="5px"
|
||||
>
|
||||
Used by 80,000+ users monthly
|
||||
</Text>
|
||||
<Flex
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
mx="auto"
|
||||
>
|
||||
<Icon
|
||||
me="1px"
|
||||
w="16px"
|
||||
h="16px"
|
||||
color="orange.500"
|
||||
as={IoIosStar}
|
||||
/>
|
||||
<Icon
|
||||
me="1px"
|
||||
w="16px"
|
||||
h="16px"
|
||||
color="orange.500"
|
||||
as={IoIosStar}
|
||||
/>
|
||||
<Icon
|
||||
me="1px"
|
||||
w="16px"
|
||||
h="16px"
|
||||
color="orange.500"
|
||||
as={IoIosStar}
|
||||
/>
|
||||
<Icon
|
||||
me="1px"
|
||||
w="16px"
|
||||
h="16px"
|
||||
color="orange.500"
|
||||
as={IoIosStar}
|
||||
/>
|
||||
<Icon
|
||||
me="6px"
|
||||
w="16px"
|
||||
h="16px"
|
||||
color="orange.500"
|
||||
as={IoIosStar}
|
||||
/>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
fontWeight="800"
|
||||
h="100%"
|
||||
color={textColor}
|
||||
>
|
||||
4.9
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
<ModalCloseButton
|
||||
borderRadius="full"
|
||||
color="#120F43"
|
||||
bg="#F4F6FB !important"
|
||||
_hover={{ bg: '#E9EDF6 !important' }}
|
||||
_focus={{ bg: '#F4F6FB !important' }}
|
||||
_active={{ bg: '#F4F6FB !important' }}
|
||||
zIndex="99"
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<Flex
|
||||
w="100%"
|
||||
cursor={'pointer'}
|
||||
onClick={() => onOpen()}
|
||||
>
|
||||
<Flex
|
||||
w="100%"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Box
|
||||
color={
|
||||
activeRoute(route.path.toLowerCase())
|
||||
? activeIcon
|
||||
: inactiveColor
|
||||
}
|
||||
me="12px"
|
||||
mt="6px"
|
||||
>
|
||||
{route.icon}
|
||||
</Box>
|
||||
<Text
|
||||
me="auto"
|
||||
color={
|
||||
activeRoute(route.path.toLowerCase())
|
||||
? activeColor
|
||||
: 'gray.500'
|
||||
}
|
||||
fontWeight={
|
||||
activeRoute(route.path.toLowerCase())
|
||||
? '700'
|
||||
: '500'
|
||||
}
|
||||
letterSpacing="0px"
|
||||
fontSize="sm"
|
||||
>
|
||||
{route.name}
|
||||
</Text>
|
||||
{route.rightElement ? (
|
||||
<Flex
|
||||
border="1px solid"
|
||||
borderColor={borderColor}
|
||||
borderRadius="full"
|
||||
w="34px"
|
||||
h="34px"
|
||||
justify={'center'}
|
||||
align="center"
|
||||
color={iconColor}
|
||||
ms="auto"
|
||||
me="10px"
|
||||
>
|
||||
<Icon
|
||||
as={IoMdAdd}
|
||||
width="20px"
|
||||
height="20px"
|
||||
color="inherit"
|
||||
/>
|
||||
</Flex>
|
||||
) : null}
|
||||
<Badge
|
||||
display={{ base: 'flex', lg: 'none', xl: 'flex' }}
|
||||
colorScheme="brand"
|
||||
borderRadius="25px"
|
||||
color="brand.500"
|
||||
textTransform={'none'}
|
||||
letterSpacing="0px"
|
||||
px="8px"
|
||||
>
|
||||
PRO
|
||||
</Badge>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
) : (
|
||||
<NavLink
|
||||
href={
|
||||
route.layout ? route.layout + route.path : route.path
|
||||
}
|
||||
key={key}
|
||||
styles={{ width: '100%' }}
|
||||
>
|
||||
<Flex
|
||||
w="100%"
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Box
|
||||
color={
|
||||
activeRoute(route.path.toLowerCase())
|
||||
? activeIcon
|
||||
: inactiveColor
|
||||
}
|
||||
me="12px"
|
||||
mt="6px"
|
||||
>
|
||||
{route.icon}
|
||||
</Box>
|
||||
<Text
|
||||
me="auto"
|
||||
color={
|
||||
activeRoute(route.path.toLowerCase())
|
||||
? activeColor
|
||||
: 'gray.500'
|
||||
}
|
||||
fontWeight={
|
||||
activeRoute(route.path.toLowerCase())
|
||||
? '700'
|
||||
: '500'
|
||||
}
|
||||
letterSpacing="0px"
|
||||
fontSize="sm"
|
||||
>
|
||||
{route.name}
|
||||
</Text>
|
||||
{route.rightElement ? (
|
||||
<Flex
|
||||
border="1px solid"
|
||||
borderColor={borderColor}
|
||||
borderRadius="full"
|
||||
w="34px"
|
||||
h="34px"
|
||||
justify={'center'}
|
||||
align="center"
|
||||
color={iconColor}
|
||||
ms="auto"
|
||||
me="10px"
|
||||
>
|
||||
<Icon
|
||||
as={IoMdAdd}
|
||||
width="20px"
|
||||
height="20px"
|
||||
color="inherit"
|
||||
/>
|
||||
</Flex>
|
||||
) : null}
|
||||
</Flex>
|
||||
</NavLink>
|
||||
)}
|
||||
</HStack>
|
||||
</Flex>
|
||||
) : (
|
||||
<ListItem ms={0}>
|
||||
<Flex ps="32px" alignItems="center" mb="8px">
|
||||
<NavLink
|
||||
href={route.layout ? route.layout + route.path : route.path}
|
||||
key={key}
|
||||
>
|
||||
<Text
|
||||
color={
|
||||
activeRoute(route.path.toLowerCase())
|
||||
? activeColor
|
||||
: inactiveColor
|
||||
}
|
||||
fontWeight="500"
|
||||
fontSize="xs"
|
||||
>
|
||||
{route.name}
|
||||
</Text>
|
||||
</NavLink>
|
||||
</Flex>
|
||||
</ListItem>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
// this function creates the links from the secondary accordions (for example auth -> sign-in -> default)
|
||||
const createAccordionLinks = (routes: IRoute[]) => {
|
||||
return routes.map((route: IRoute, key: number) => {
|
||||
return (
|
||||
<ListItem
|
||||
ms="28px"
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
mb="10px"
|
||||
key={key}
|
||||
>
|
||||
<NavLink href={route.layout + route.path} key={key}>
|
||||
<Icon w="6px" h="6px" me="8px" as={FaCircle} color={activeIcon} />
|
||||
<Text
|
||||
color={
|
||||
activeRoute(route.path.toLowerCase())
|
||||
? activeColor
|
||||
: inactiveColor
|
||||
}
|
||||
fontWeight={
|
||||
activeRoute(route.path.toLowerCase()) ? 'bold' : 'normal'
|
||||
}
|
||||
fontSize="sm"
|
||||
>
|
||||
{route.name}
|
||||
</Text>
|
||||
</NavLink>
|
||||
</ListItem>
|
||||
);
|
||||
});
|
||||
};
|
||||
// BRAND
|
||||
return <>{createLinks(routes)}</>;
|
||||
}
|
||||
|
||||
export default SidebarLinks;
|
||||
@@ -0,0 +1,556 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
ProductsContext,
|
||||
SubscriptionContext,
|
||||
UserContext
|
||||
} from '@/contexts/layout';
|
||||
import modalImage from '@/public/Modal.png';
|
||||
import SidebarImage from '@/public/SidebarBadge.png';
|
||||
import { Database } from '@/types_db';
|
||||
import { getErrorRedirect } from '@/utils/helpers';
|
||||
import { getStripe } from '@/utils/stripe/client';
|
||||
import { checkoutWithStripe } from '@/utils/stripe/server';
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Flex,
|
||||
Icon,
|
||||
Image,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalOverlay,
|
||||
Text,
|
||||
useColorModeValue,
|
||||
useDisclosure
|
||||
} from '@chakra-ui/react';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import { useContext, useState } from 'react';
|
||||
import { IoIosStar } from 'react-icons/io';
|
||||
import { MdCheckCircle, MdChevronRight } from 'react-icons/md';
|
||||
|
||||
type Price = Database['public']['Tables']['prices']['Row'];
|
||||
interface SidebarCard {
|
||||
[x: string]: any;
|
||||
}
|
||||
export default function SidebarCard(props: SidebarCard) {
|
||||
const [priceIdLoading, setPriceIdLoading] = useState<string>();
|
||||
const textColor = useColorModeValue('#120F43', 'white');
|
||||
const currentPath = usePathname();
|
||||
const products = useContext(ProductsContext);
|
||||
const subscription = useContext(SubscriptionContext);
|
||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||
const [plan, setPlan] = useState({
|
||||
product: 'prod_PtTCPDFZbburMa',
|
||||
price: 'price_1P3gGXGx8VbJPRgzdEZODy8K'
|
||||
});
|
||||
const borderColor = 'secondaryGray.200';
|
||||
const router = useRouter();
|
||||
const handleCheckout = async (price: Price) => {
|
||||
setPriceIdLoading(price.id);
|
||||
const user = useContext(UserContext);
|
||||
|
||||
if (!user) {
|
||||
setPriceIdLoading(undefined);
|
||||
return router.push('/dashboard/signin/signup');
|
||||
}
|
||||
|
||||
const { errorRedirect, sessionId } = await checkoutWithStripe(
|
||||
price,
|
||||
currentPath
|
||||
);
|
||||
|
||||
if (errorRedirect) {
|
||||
setPriceIdLoading(undefined);
|
||||
return router.push(errorRedirect);
|
||||
}
|
||||
|
||||
if (!sessionId) {
|
||||
setPriceIdLoading(undefined);
|
||||
return router.push(
|
||||
getErrorRedirect(
|
||||
currentPath,
|
||||
'An unknown error occurred.',
|
||||
'Please try again later or contact a system administrator.'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const stripe = await getStripe();
|
||||
stripe?.redirectToCheckout({ sessionId });
|
||||
|
||||
setPriceIdLoading(undefined);
|
||||
};
|
||||
|
||||
if (subscription) {
|
||||
// -------------- PRO User Card --------------
|
||||
return (
|
||||
<Flex
|
||||
align="center"
|
||||
position="relative"
|
||||
border="1px solid"
|
||||
borderColor={borderColor}
|
||||
borderRadius="16px"
|
||||
w="100%"
|
||||
py="16px"
|
||||
px="14px"
|
||||
>
|
||||
<Image alt=" " src={SidebarImage.src} maxW="27px" me="10px" />
|
||||
<Flex direction="column" justify="center" w="100%">
|
||||
<Text fontSize="sm" fontWeight={'700'} color="#120F43" mb="2x">
|
||||
PRO Member
|
||||
</Text>
|
||||
<Text fontWeight={'500'} fontSize="sm" color="gray.500">
|
||||
Unlimited plan active
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
} else {
|
||||
// -------------- Free User Card --------------
|
||||
return (
|
||||
<Flex
|
||||
justify="center"
|
||||
direction="column"
|
||||
align="center"
|
||||
position="relative"
|
||||
border="1px solid"
|
||||
borderColor={borderColor}
|
||||
borderRadius="16px"
|
||||
w="100%"
|
||||
py="20px"
|
||||
px="18px"
|
||||
>
|
||||
<Image alt=" " src={SidebarImage.src} maxW="54px" />
|
||||
<Flex direction="column" mb="12px" w="100%" pt="16px">
|
||||
<Text
|
||||
fontSize="lg"
|
||||
fontWeight={'700'}
|
||||
color="#120F43"
|
||||
mb="10px"
|
||||
textAlign={'center'}
|
||||
>
|
||||
Upgrade to Unlimited
|
||||
</Text>
|
||||
<Text
|
||||
textAlign={'center'}
|
||||
fontWeight={'500'}
|
||||
fontSize="sm"
|
||||
color="gray.500"
|
||||
mb="14px"
|
||||
>
|
||||
Generate premium Essays by upgrading to an unlimited plan!
|
||||
</Text>
|
||||
</Flex>
|
||||
<Button
|
||||
onClick={() => {
|
||||
onOpen();
|
||||
}}
|
||||
py="20px"
|
||||
px="16px"
|
||||
fontSize="sm"
|
||||
variant="primary"
|
||||
borderRadius="45px"
|
||||
w={{ base: '100%' }}
|
||||
h="54px"
|
||||
mb="14px"
|
||||
_hover={{
|
||||
boxShadow: '0px 21px 27px -10px rgba(96, 60, 255, 0.48) !important',
|
||||
bg:
|
||||
'linear-gradient(15.46deg, #4A25E1 26.3%, #7B5AFF 86.4%) !important',
|
||||
_disabled: {
|
||||
bg: 'linear-gradient(15.46deg, #4A25E1 26.3%, #7B5AFF 86.4%)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Go unlimited for just $9
|
||||
</Button>
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<ModalOverlay bg="rgba(0, 0, 0, 0.85)" />
|
||||
<ModalContent
|
||||
mx="8px"
|
||||
bg="transparent"
|
||||
boxShadow="unset"
|
||||
maxW="unset"
|
||||
w="unset"
|
||||
>
|
||||
<ModalBody p="0px" position={'relative'}>
|
||||
<Flex>
|
||||
<Image
|
||||
display={{ base: 'none', md: 'block' }}
|
||||
zIndex="98"
|
||||
borderLeftRadius="16px"
|
||||
src={modalImage.src}
|
||||
w="340px"
|
||||
alt=" "
|
||||
/>
|
||||
<Flex
|
||||
bg="white"
|
||||
borderLeftRadius={{ base: '16px', md: '0px' }}
|
||||
borderRightRadius="16px"
|
||||
direction={'column'}
|
||||
px={{ base: '30px', md: '42px' }}
|
||||
py="34px"
|
||||
w={{ md: '412px', lg: '456px' }}
|
||||
minW={{ md: '412px', lg: '456px' }}
|
||||
>
|
||||
<Text
|
||||
fontSize="26px"
|
||||
fontWeight={'800'}
|
||||
color={textColor}
|
||||
mb="12px"
|
||||
>
|
||||
Upgrade to Unlimited
|
||||
</Text>
|
||||
<Text
|
||||
mb="24px"
|
||||
fontWeight="500"
|
||||
fontSize="md"
|
||||
color="gray.500"
|
||||
>
|
||||
Get access to all features and generate premium and
|
||||
exclusive essays with our unlimited plan!
|
||||
</Text>
|
||||
{/* Features */}
|
||||
<Flex w={{ base: '100%', xl: '80%' }} direction="column">
|
||||
<Flex alignItems="center" mb="20px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={'green.500'}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize="sm"
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Access to 12+ Essay types
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="20px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={'green.500'}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize="sm"
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Up to 1500 words per Essay
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="20px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={'green.500'}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize="sm"
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Academic Citation formats (APA, etc.)
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="20px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={'green.500'}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize="sm"
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Academic Levels (Master, etc.)
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex alignItems="center" mb="30px">
|
||||
<Icon
|
||||
me="10px"
|
||||
w="20px"
|
||||
h="20px"
|
||||
color={'green.500'}
|
||||
as={MdCheckCircle}
|
||||
/>
|
||||
<Text
|
||||
as="span"
|
||||
fontSize="sm"
|
||||
fontWeight="600"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
>
|
||||
Essay Tones (Academic, etc.)
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
{/* YEARLY */}
|
||||
<Flex
|
||||
onClick={() =>
|
||||
setPlan({
|
||||
product: 'prod_PtTJ6R3RnzmIPX',
|
||||
price: 'price_1P3gMyGx8VbJPRgzkoB6Fp8F'
|
||||
})
|
||||
}
|
||||
transition="0.15s linear"
|
||||
align="center"
|
||||
position="relative"
|
||||
border="1px solid"
|
||||
borderColor={
|
||||
plan.product === 'prod_PtTJ6R3RnzmIPX'
|
||||
? 'brand.500'
|
||||
: borderColor
|
||||
}
|
||||
borderRadius="10px"
|
||||
w="100%"
|
||||
py="14px"
|
||||
px="14px"
|
||||
cursor="pointer"
|
||||
mb="20px"
|
||||
>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
fontWeight={'700'}
|
||||
color="#120F43"
|
||||
mb="2x"
|
||||
ms="8px"
|
||||
me="8px"
|
||||
>
|
||||
Yearly
|
||||
</Text>
|
||||
<Badge
|
||||
display={{
|
||||
base: 'flex',
|
||||
lg: 'none',
|
||||
xl: 'flex'
|
||||
}}
|
||||
colorScheme="green"
|
||||
borderRadius="4px"
|
||||
color="green.500"
|
||||
textTransform={'none'}
|
||||
letterSpacing="0px"
|
||||
px="0px"
|
||||
w="max-content"
|
||||
>
|
||||
Save 35%
|
||||
</Badge>
|
||||
<Text
|
||||
display="flex"
|
||||
ms="auto"
|
||||
fontSize="md"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
fontWeight="600"
|
||||
lineHeight="100%"
|
||||
>
|
||||
$5.75
|
||||
<Text
|
||||
fontSize={'14px'}
|
||||
color="gray.500"
|
||||
fontWeight="500"
|
||||
ms="4px"
|
||||
as="span"
|
||||
>
|
||||
/month
|
||||
</Text>
|
||||
</Text>
|
||||
</Flex>
|
||||
{/* END YEARLY */}
|
||||
{/* MONTHLY */}
|
||||
<Flex
|
||||
onClick={() =>
|
||||
setPlan({
|
||||
product: 'prod_PtTCPDFZbburMa',
|
||||
price: 'price_1P3gGXGx8VbJPRgzdEZODy8K'
|
||||
})
|
||||
}
|
||||
transition="0.15s linear"
|
||||
align="center"
|
||||
position="relative"
|
||||
border="1px solid"
|
||||
borderColor={
|
||||
plan.product === 'prod_PtTCPDFZbburMa'
|
||||
? 'brand.500'
|
||||
: borderColor
|
||||
}
|
||||
borderRadius="10px"
|
||||
w="100%"
|
||||
py="16px"
|
||||
px="14px"
|
||||
cursor="pointer"
|
||||
mb="28px"
|
||||
>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
fontWeight={'700'}
|
||||
color="#120F43"
|
||||
mb="2x"
|
||||
ms="8px"
|
||||
me="4px"
|
||||
>
|
||||
Monthly
|
||||
</Text>
|
||||
<Text
|
||||
display="flex"
|
||||
ms="auto"
|
||||
fontSize="md"
|
||||
color={textColor}
|
||||
letterSpacing="0px"
|
||||
fontWeight="600"
|
||||
lineHeight="100%"
|
||||
>
|
||||
$9
|
||||
<Text
|
||||
fontSize={'14px'}
|
||||
color="gray.500"
|
||||
fontWeight="500"
|
||||
ms="4px"
|
||||
as="span"
|
||||
>
|
||||
/month
|
||||
</Text>
|
||||
</Text>
|
||||
</Flex>
|
||||
{/* END MONTHLY */}
|
||||
{products.map((product: any) => {
|
||||
const price = product?.prices?.find(
|
||||
(price: any) => price.id === plan.price
|
||||
);
|
||||
if (product.id === plan.product) {
|
||||
if (!price) return null;
|
||||
return (
|
||||
<Button
|
||||
key={product.id}
|
||||
py="20px"
|
||||
px="16px"
|
||||
fontSize="sm"
|
||||
variant="primary"
|
||||
borderRadius="45px"
|
||||
w={{ base: '100%' }}
|
||||
h="54px"
|
||||
mb="28px"
|
||||
_hover={{
|
||||
boxShadow:
|
||||
'0px 21px 27px -10px rgba(96, 60, 255, 0.48) !important',
|
||||
bg:
|
||||
'linear-gradient(15.46deg, #4A25E1 26.3%, #7B5AFF 86.4%) !important',
|
||||
_disabled: {
|
||||
bg:
|
||||
'linear-gradient(15.46deg, #4A25E1 26.3%, #7B5AFF 86.4%)'
|
||||
}
|
||||
}}
|
||||
onClick={() => handleCheckout(price)}
|
||||
>
|
||||
Upgrade now
|
||||
<Icon
|
||||
as={MdChevronRight}
|
||||
mt="2px"
|
||||
h="16px"
|
||||
w="16px"
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
})}
|
||||
<Text
|
||||
fontSize="xs"
|
||||
color="gray.500"
|
||||
fontWeight={'500'}
|
||||
mx="auto"
|
||||
mb="5px"
|
||||
>
|
||||
Used by 80,000+ users monthly
|
||||
</Text>
|
||||
<Flex direction="row" alignItems="center" mx="auto">
|
||||
<Icon
|
||||
me="1px"
|
||||
w="16px"
|
||||
h="16px"
|
||||
color="orange.500"
|
||||
as={IoIosStar}
|
||||
/>
|
||||
<Icon
|
||||
me="1px"
|
||||
w="16px"
|
||||
h="16px"
|
||||
color="orange.500"
|
||||
as={IoIosStar}
|
||||
/>
|
||||
<Icon
|
||||
me="1px"
|
||||
w="16px"
|
||||
h="16px"
|
||||
color="orange.500"
|
||||
as={IoIosStar}
|
||||
/>
|
||||
<Icon
|
||||
me="1px"
|
||||
w="16px"
|
||||
h="16px"
|
||||
color="orange.500"
|
||||
as={IoIosStar}
|
||||
/>
|
||||
<Icon
|
||||
me="6px"
|
||||
w="16px"
|
||||
h="16px"
|
||||
color="orange.500"
|
||||
as={IoIosStar}
|
||||
/>
|
||||
<Text
|
||||
fontSize="sm"
|
||||
fontWeight="800"
|
||||
h="100%"
|
||||
color={textColor}
|
||||
>
|
||||
4.9
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
<ModalCloseButton
|
||||
borderRadius="full"
|
||||
color="#120F43"
|
||||
bg="#F4F6FB !important"
|
||||
_hover={{ bg: '#E9EDF6 !important' }}
|
||||
_focus={{ bg: '#F4F6FB !important' }}
|
||||
_active={{ bg: '#F4F6FB !important' }}
|
||||
zIndex="99"
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<Text
|
||||
textAlign={'center'}
|
||||
fontWeight={'500'}
|
||||
fontSize="xs"
|
||||
color="gray.500"
|
||||
>
|
||||
Join 80,000+ users now
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
53
boilerplate-chakra-pro-main/contexts/layout.ts
Normal file
53
boilerplate-chakra-pro-main/contexts/layout.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { createContext } from 'react';
|
||||
import type { Tables } from '@/types/types_db';
|
||||
import { User } from '@supabase/supabase-js';
|
||||
|
||||
interface PlanContextType {
|
||||
plan: {
|
||||
product: string;
|
||||
price: string;
|
||||
};
|
||||
setPlan: React.Dispatch<
|
||||
React.SetStateAction<{
|
||||
product: string;
|
||||
price: string;
|
||||
}>
|
||||
>;
|
||||
}
|
||||
interface ApiKeyContextType {
|
||||
apiKey: string;
|
||||
setApiKey: React.Dispatch<React.SetStateAction<string>>;
|
||||
}
|
||||
|
||||
interface OpenContextType {
|
||||
open: boolean;
|
||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
type Subscription = Tables<'subscriptions'>;
|
||||
type Product = Tables<'products'>;
|
||||
type Price = Tables<'prices'>;
|
||||
|
||||
interface ProductWithPrices extends Product {
|
||||
prices: Price[];
|
||||
}
|
||||
interface PriceWithProduct extends Price {
|
||||
products: Product | null;
|
||||
}
|
||||
interface SubscriptionWithProduct extends Subscription {
|
||||
prices: PriceWithProduct | null;
|
||||
}
|
||||
type UserDetails = { [x: string]: any } | null;
|
||||
|
||||
export const PlanContext = createContext<PlanContextType>(undefined);
|
||||
export const ApiKeyContext = createContext<ApiKeyContextType>(undefined);
|
||||
export const OpenContext = createContext<OpenContextType>(undefined);
|
||||
export const ProductsContext = createContext<ProductWithPrices[] | undefined>(
|
||||
undefined
|
||||
);
|
||||
export const SubscriptionContext = createContext<
|
||||
SubscriptionWithProduct | any | null
|
||||
>(undefined);
|
||||
export const UserContext = createContext<User | undefined | null>(undefined);
|
||||
export const UserDetailsContext = createContext<UserDetails | undefined | null>(
|
||||
undefined
|
||||
);
|
||||
85
boilerplate-chakra-pro-main/fixtures/stripe-fixtures.json
Normal file
85
boilerplate-chakra-pro-main/fixtures/stripe-fixtures.json
Normal file
@@ -0,0 +1,85 @@
|
||||
{
|
||||
"_meta": {
|
||||
"template_version": 0
|
||||
},
|
||||
"fixtures": [
|
||||
{
|
||||
"name": "prod_hobby",
|
||||
"path": "/v1/products",
|
||||
"method": "post",
|
||||
"params": {
|
||||
"name": "Hobby",
|
||||
"description": "Hobby product description"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "price_hobby_month",
|
||||
"path": "/v1/prices",
|
||||
"method": "post",
|
||||
"params": {
|
||||
"product": "${prod_hobby:id}",
|
||||
"currency": "usd",
|
||||
"billing_scheme": "per_unit",
|
||||
"unit_amount": 1000,
|
||||
"recurring": {
|
||||
"interval": "month",
|
||||
"interval_count": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "price_hobby_year",
|
||||
"path": "/v1/prices",
|
||||
"method": "post",
|
||||
"params": {
|
||||
"product": "${prod_hobby:id}",
|
||||
"currency": "usd",
|
||||
"billing_scheme": "per_unit",
|
||||
"unit_amount": 10000,
|
||||
"recurring": {
|
||||
"interval": "year",
|
||||
"interval_count": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "prod_freelancer",
|
||||
"path": "/v1/products",
|
||||
"method": "post",
|
||||
"params": {
|
||||
"name": "Freelancer",
|
||||
"description": "Freelancer product description"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "price_freelancer_month",
|
||||
"path": "/v1/prices",
|
||||
"method": "post",
|
||||
"params": {
|
||||
"product": "${prod_freelancer:id}",
|
||||
"currency": "usd",
|
||||
"billing_scheme": "per_unit",
|
||||
"unit_amount": 2000,
|
||||
"recurring": {
|
||||
"interval": "month",
|
||||
"interval_count": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "price_freelancer_year",
|
||||
"path": "/v1/prices",
|
||||
"method": "post",
|
||||
"params": {
|
||||
"product": "${prod_freelancer:id}",
|
||||
"currency": "usd",
|
||||
"billing_scheme": "per_unit",
|
||||
"unit_amount": 20000,
|
||||
"recurring": {
|
||||
"interval": "year",
|
||||
"interval_count": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
9
boilerplate-chakra-pro-main/jsconfig.json
Normal file
9
boilerplate-chakra-pro-main/jsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react",
|
||||
"baseUrl": "src",
|
||||
"paths": {
|
||||
"*": ["src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
20
boilerplate-chakra-pro-main/middleware.ts
Normal file
20
boilerplate-chakra-pro-main/middleware.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { type NextRequest } from 'next/server';
|
||||
import { updateSession } from '@/utils/supabase/middleware';
|
||||
|
||||
export async function middleware(request: NextRequest) {
|
||||
return await updateSession(request);
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: [
|
||||
/*
|
||||
* Match all request paths except:
|
||||
* - _next/static (static files)
|
||||
* - _next/image (image optimization files)
|
||||
* - favicon.ico (favicon file)
|
||||
* - images - .svg, .png, .jpg, .jpeg, .gif, .webp
|
||||
* Feel free to modify this pattern to include more paths.
|
||||
*/
|
||||
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)'
|
||||
]
|
||||
};
|
||||
21
boilerplate-chakra-pro-main/next.config.js
Normal file
21
boilerplate-chakra-pro-main/next.config.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const nextConfig = {
|
||||
reactStrictMode: false, // changed this to false
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: 'lh3.googleusercontent.com',
|
||||
port: '',
|
||||
pathname: '/a/**'
|
||||
},
|
||||
{
|
||||
protocol: 'https',
|
||||
hostname: '*.googleusercontent.com',
|
||||
port: '',
|
||||
pathname: '**'
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user