├── .env.example
├── .eslintrc.json
├── .github
└── workflows
│ └── npm-gulp.yml
├── .gitignore
├── LICENSE
├── README.md
├── app
├── (auth)
│ ├── (routes)
│ │ ├── sign-in
│ │ │ └── [[...sign-in]]
│ │ │ │ └── page.tsx
│ │ └── sign-up
│ │ │ └── [[...sign-up]]
│ │ │ └── page.tsx
│ └── layout.tsx
├── (dashboard)
│ ├── (routes)
│ │ ├── code
│ │ │ ├── constants.tsx
│ │ │ └── page.tsx
│ │ ├── conversation
│ │ │ ├── constants.tsx
│ │ │ └── page.tsx
│ │ ├── dashboard
│ │ │ └── page.tsx
│ │ ├── image
│ │ │ ├── constants.tsx
│ │ │ └── page.tsx
│ │ ├── music
│ │ │ ├── constants.tsx
│ │ │ └── page.tsx
│ │ ├── settings
│ │ │ └── page.tsx
│ │ └── video
│ │ │ ├── constants.tsx
│ │ │ └── page.tsx
│ └── layout.tsx
├── (landing)
│ ├── layout.tsx
│ └── page.tsx
├── api
│ ├── code
│ │ └── route.ts
│ ├── conversation
│ │ └── route.ts
│ ├── image
│ │ └── route.ts
│ ├── music
│ │ └── route.ts
│ ├── stripe
│ │ └── route.ts
│ ├── video
│ │ └── route.ts
│ └── webhook
│ │ └── route.ts
├── favicon.ico
├── globals.css
└── layout.tsx
├── components.json
├── components
├── bot-avatar.tsx
├── crisp-chat.tsx
├── crisp-provider.tsx
├── empty.tsx
├── free-counter.tsx
├── heading.tsx
├── landing-content.tsx
├── landing-hero.tsx
├── landing-navbar.tsx
├── loader.tsx
├── mobile-sidebar.tsx
├── modal-provider.tsx
├── navbar.tsx
├── pro-modal.tsx
├── sidebar.tsx
├── subscription-button.tsx
├── toaster-provider.tsx
├── ui
│ ├── avatar.tsx
│ ├── badge.tsx
│ ├── button.tsx
│ ├── card.tsx
│ ├── dialog.tsx
│ ├── form.tsx
│ ├── input.tsx
│ ├── label.tsx
│ ├── progress.tsx
│ ├── select.tsx
│ └── sheet.tsx
└── user-avatar.tsx
├── constants.ts
├── hooks
└── use-pro-modal.tsx
├── lib
├── api-limit.ts
├── prismadb.ts
├── stripe.ts
├── subscription.ts
└── utils.ts
├── middleware.ts
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── prisma
└── schema.prisma
├── public
├── empty.png
├── logo.png
├── next.svg
└── vercel.svg
├── tailwind.config.js
└── tsconfig.json
/.env.example:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your_api_key
2 | CLERK_SECRET_KEY=your_secret_key
3 |
4 | NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
5 | NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
6 | NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
7 | NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard
8 |
9 | OPENAI_API_KEY=your_api_key
10 |
11 | REPLICATE_API_TOKEN=your_api_token
12 |
13 | DATABASE_URL=your_database_connection_string
14 |
15 | STRIPE_API_KEY=your_stripe_api_key
16 |
17 | NEXT_PUBLIC_APP_URL=http://localhost:3000
18 |
19 | CRISP_WEBSITE_ID=your_crisp_website_id
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/.github/workflows/npm-gulp.yml:
--------------------------------------------------------------------------------
1 | name: NodeJS with Gulp
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | branches: [ "main" ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | node-version: [14.x, 16.x, 18.x]
16 |
17 | steps:
18 | - uses: actions/checkout@v4
19 |
20 | - name: Use Node.js ${{ matrix.node-version }}
21 | uses: actions/setup-node@v3
22 | with:
23 | node-version: ${{ matrix.node-version }}
24 |
25 | - name: Build
26 | run: |
27 | npm install
28 | gulp
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 | .env
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Ayush Rathore
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AI-SaaS - AI-Powered Software-as-a-Service Application
2 |
3 | [](https://opensource.org/licenses/MIT)
4 | [](https://opensource.org/licenses/MIT)
5 | [](https://nextjs.org/)
6 | [](https://openai.com/)
7 | [](https://replicate.ai/)
8 | [](https://tailwindcss.com/)
9 | [](https://prisma.io/)
10 | [](https://stripe.com/)
11 |
12 | AI-SaaS is an advanced and adaptable Software-as-a-Service (SaaS) application that harnesses the capabilities of cutting-edge technologies, including Next.js, OpenAI, Replicate, Tailwind CSS, Prisma, and Stripe. The primary goal of this application is to empower users by offering AI-powered services that facilitate easy access and utilization of artificial intelligence in their projects and workflows.
13 |
14 | ## Features
15 |
16 | - **AI Services**: AI-SaaS provides an extensive array of AI services, including conversation, code generation, image generation, music generation, and video generation. These services are accessible through an intuitive and user-friendly interface.
17 |
18 | - **Next.js**: AI-SaaS is built on the Next.js framework, offering server-side rendering, routing, and other essential features out of the box. This ensures superior performance and search engine optimization (SEO) for the application.
19 |
20 | - **OpenAI Integration**: The application seamlessly integrates with OpenAI's powerful AI models and APIs, enabling users to leverage state-of-the-art AI capabilities. From generating human-like text to answering questions, AI-SaaS harnesses the full potential of OpenAI.
21 |
22 | - **Replicate**: AI-SaaS employs Replicate to enhance model reproducibility and facilitate seamless experimentation with various AI models. This ensures the AI models used in the application are robust and reliable.
23 |
24 | - **Tailwind CSS**: The UI of AI-SaaS is meticulously styled using Tailwind CSS, a utility-first CSS framework. This enables easy customization and consistent design throughout the application.
25 |
26 | - **Prisma**: The application utilizes Prisma as its ORM (Object-Relational Mapping) tool, simplifying database access and management. This enhances the efficiency of handling user data and preferences.
27 |
28 | - **Stripe Integration**: AI-SaaS seamlessly incorporates Stripe for secure and efficient payment processing. Users can subscribe to premium plans and access additional AI services based on their subscription level.
29 |
30 | ## Screenshots
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | ## Getting Started
40 |
41 | To run AI-SaaS locally, follow these steps:
42 |
43 | 1. **Clone the repository**:
44 |
45 | ```bash
46 | git clone https://github.com/ayusshrathore/ai-saas.git
47 | cd ai-saas
48 | ```
49 |
50 | 2. **Install dependencies**:
51 |
52 | ```bash
53 | npm install
54 | # or
55 | yarn install
56 | ```
57 |
58 | 3. **Configure environment variables**:
59 |
60 | To ensure proper functionality, set up environment variables for API keys and other sensitive information. Create a `.env` file in the root directory and populate it with the necessary variables. For reference, consult the `.env.example` file for the required variables.
61 |
62 | 4. **Run the application**:
63 |
64 | ```bash
65 | npm run dev
66 | # or
67 | yarn dev
68 | ```
69 |
70 | The application should now be running locally at `http://localhost:3000`.
71 |
72 | ## Deployment
73 |
74 | AI-SaaS can be deployed to various hosting platforms that support Next.js applications. Before deployment, make sure you have configured the necessary environment variables for production.
75 |
76 | ## Contributions
77 |
78 | Contributions to AI-SaaS are highly appreciated! If you encounter any bugs or have suggestions for new features, please feel free to open an issue or submit a pull request.
79 |
80 | When contributing, adhere to the existing code style and include comprehensive test cases for new features.
81 |
82 | ## License
83 |
84 | AI-SaaS is released under the [MIT License](https://opensource.org/licenses/MIT).
85 |
86 | ## Acknowledgments
87 |
88 | AI-SaaS is built with the invaluable support and integration of several open-source projects and technologies. I extend my gratitude to the developers and maintainers of Next.js, OpenAI, Replicate, Tailwind CSS, Prisma, and Stripe for their significant contributions to the development community.
89 | [](https://app.netlify.com/sites/superlative-malabi-796b55/deploys)
90 |
--------------------------------------------------------------------------------
/app/(auth)/(routes)/sign-in/[[...sign-in]]/page.tsx:
--------------------------------------------------------------------------------
1 | import { SignIn } from "@clerk/nextjs";
2 |
3 | export default function Page() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/app/(auth)/(routes)/sign-up/[[...sign-up]]/page.tsx:
--------------------------------------------------------------------------------
1 | import { SignUp } from "@clerk/nextjs";
2 |
3 | export default function Page() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/app/(auth)/layout.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const AuthLayout = ({ children }: { children: React.ReactNode }) => {
4 | return
{children}
;
5 | };
6 |
7 | export default AuthLayout;
8 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/code/constants.tsx:
--------------------------------------------------------------------------------
1 | import * as z from "zod";
2 |
3 | export const formSchema = z.object({
4 | prompt: z.string().min(1, {
5 | message: "Prompt is required.",
6 | }),
7 | });
8 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/code/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { zodResolver } from "@hookform/resolvers/zod";
4 | import axios from "axios";
5 | import { Code } from "lucide-react";
6 | import { useRouter } from "next/navigation";
7 | import { ChatCompletionRequestMessage } from "openai";
8 | import { useState } from "react";
9 | import { useForm } from "react-hook-form";
10 | import ReactMarkdown from "react-markdown";
11 | import * as z from "zod";
12 |
13 | import { BotAvatar } from "@/components/bot-avatar";
14 | import { Empty } from "@/components/empty";
15 | import { Heading } from "@/components/heading";
16 | import { Loader } from "@/components/loader";
17 | import { Button } from "@/components/ui/button";
18 | import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
19 | import { Input } from "@/components/ui/input";
20 | import { UserAvatar } from "@/components/user-avatar";
21 | import { cn } from "@/lib/utils";
22 |
23 | import useProModal from "@/hooks/use-pro-modal";
24 | import { toast } from "react-hot-toast";
25 | import { formSchema } from "./constants";
26 |
27 | const CodePage = () => {
28 | const router = useRouter();
29 | const proModal = useProModal();
30 | const [messages, setMessages] = useState([]);
31 |
32 | const form = useForm>({
33 | resolver: zodResolver(formSchema),
34 | defaultValues: {
35 | prompt: "",
36 | },
37 | });
38 |
39 | const isLoading = form.formState.isSubmitting;
40 |
41 | const onSubmit = async (values: z.infer) => {
42 | console.log(values);
43 | try {
44 | const userMessage: ChatCompletionRequestMessage = {
45 | role: "user",
46 | content: values.prompt,
47 | };
48 | const newMessages = [...messages, userMessage];
49 |
50 | const response = await axios.post("/api/code", {
51 | messages: newMessages,
52 | });
53 |
54 | setMessages((current) => [...current, userMessage, response.data]);
55 | form.reset();
56 | } catch (error: any) {
57 | console.log(error);
58 | if (error?.response?.status === 403) {
59 | proModal.onOpen();
60 | } else {
61 | toast.error("Something went wrong.");
62 | }
63 | } finally {
64 | router.refresh();
65 | }
66 | };
67 |
68 | return (
69 |
70 |
77 |
78 |
79 |
103 |
104 |
105 |
106 | {isLoading && (
107 |
108 |
109 |
110 | )}
111 | {messages.length === 0 && !isLoading &&
}
112 |
113 | {messages.map((message, index) => (
114 |
121 | {message.role === "user" ?
:
}
122 |
(
126 |
129 | ),
130 | code: ({ node, ...props }) =>
,
131 | }}
132 | >
133 | {message.content || ""}
134 |
135 |
136 | ))}
137 |
138 |
139 |
140 |
141 | );
142 | };
143 |
144 | export default CodePage;
145 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/conversation/constants.tsx:
--------------------------------------------------------------------------------
1 | import * as z from "zod";
2 |
3 | export const formSchema = z.object({
4 | prompt: z.string().min(1, {
5 | message: "Prompt is required.",
6 | }),
7 | });
8 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/conversation/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { zodResolver } from "@hookform/resolvers/zod";
4 | import axios from "axios";
5 | import { MessageSquare } from "lucide-react";
6 | import { useRouter } from "next/navigation";
7 | import { ChatCompletionRequestMessage } from "openai";
8 | import { useState } from "react";
9 | import { useForm } from "react-hook-form";
10 | import * as z from "zod";
11 |
12 | import { BotAvatar } from "@/components/bot-avatar";
13 | import { Empty } from "@/components/empty";
14 | import { Heading } from "@/components/heading";
15 | import { Loader } from "@/components/loader";
16 | import { Button } from "@/components/ui/button";
17 | import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
18 | import { Input } from "@/components/ui/input";
19 | import { UserAvatar } from "@/components/user-avatar";
20 | import { cn } from "@/lib/utils";
21 |
22 | import useProModal from "@/hooks/use-pro-modal";
23 | import { toast } from "react-hot-toast";
24 | import { formSchema } from "./constants";
25 |
26 | const ConversationPage = () => {
27 | const router = useRouter();
28 | const proModal = useProModal();
29 | const [messages, setMessages] = useState([]);
30 |
31 | const form = useForm>({
32 | resolver: zodResolver(formSchema),
33 | defaultValues: {
34 | prompt: "",
35 | },
36 | });
37 |
38 | const isLoading = form.formState.isSubmitting;
39 |
40 | const onSubmit = async (values: z.infer) => {
41 | console.log(values);
42 | try {
43 | const userMessage: ChatCompletionRequestMessage = {
44 | role: "user",
45 | content: values.prompt,
46 | };
47 | const newMessages = [...messages, userMessage];
48 |
49 | const response = await axios.post("/api/conversation", {
50 | messages: newMessages,
51 | });
52 |
53 | setMessages((current) => [...current, userMessage, response.data]);
54 | form.reset();
55 | } catch (error: any) {
56 | console.log(error);
57 | if (error?.response?.status === 403) {
58 | proModal.onOpen();
59 | } else {
60 | toast.error("Something went wrong.");
61 | }
62 | } finally {
63 | router.refresh();
64 | }
65 | };
66 |
67 | return (
68 |
69 |
76 |
77 |
78 |
102 |
103 |
104 |
105 | {isLoading && (
106 |
107 |
108 |
109 | )}
110 | {messages.length === 0 && !isLoading &&
}
111 |
112 | {messages.map((message, index) => (
113 |
120 | {message.role === "user" ?
:
}
121 |
{message.content}
122 |
123 | ))}
124 |
125 |
126 |
127 |
128 | );
129 | };
130 |
131 | export default ConversationPage;
132 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/dashboard/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import {
4 | ArrowRight,
5 | Code,
6 | ImageIcon,
7 | MessageSquare,
8 | Music,
9 | VideoIcon,
10 | } from "lucide-react";
11 | import { useRouter } from "next/navigation";
12 |
13 | import { Card } from "@/components/ui/card";
14 | import { cn } from "@/lib/utils";
15 |
16 | const tools = [
17 | {
18 | label: "Conversation",
19 | icon: MessageSquare,
20 | color: "text-violet-500",
21 | bgColor: "bg-violet-500/10",
22 | href: "/conversation",
23 | },
24 | {
25 | label: "Music Generation",
26 | icon: Music,
27 | color: "text-emerald-500",
28 | bgColor: "bg-emerald-500/10",
29 | href: "/music",
30 | },
31 | {
32 | label: "Image Generation",
33 | icon: ImageIcon,
34 | color: "text-pink-700",
35 | bgColor: "bg-pink-700/10",
36 | href: "/image",
37 | },
38 | {
39 | label: "Video Generation",
40 | icon: VideoIcon,
41 | color: "text-orange-700",
42 | bgColor: "bg-orange-700/10",
43 | href: "/video",
44 | },
45 | {
46 | label: "Code Generation",
47 | icon: Code,
48 | color: "text-green-700",
49 | bgColor: "bg-green-700/10",
50 | href: "/code",
51 | },
52 | ];
53 |
54 | const DashboardPage = () => {
55 | const router = useRouter();
56 |
57 | return (
58 |
59 |
60 |
61 | Explore the power of AI
62 |
63 |
64 | Prometheus is a platform that allows you to generate music, videos,
65 | and code using the power of AI.
66 |
67 |
68 |
69 | {tools.map((tool) => (
70 |
router.push(tool.href)}
72 | key={tool.href}
73 | className={
74 | "p-4 border-black/5 flex items-center justify-between hover:shadow-md transition cursor-pointer"
75 | }
76 | >
77 |
78 |
79 |
80 |
81 |
{tool.label}
82 |
83 |
84 |
85 | ))}
86 |
87 |
88 | );
89 | };
90 |
91 | export default DashboardPage;
92 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/image/constants.tsx:
--------------------------------------------------------------------------------
1 | import * as z from "zod";
2 |
3 | export const formSchema = z.object({
4 | prompt: z.string().min(1, {
5 | message: "Image Prompt is required.",
6 | }),
7 | amount: z.string().min(1),
8 | resolution: z.string().min(1),
9 | });
10 |
11 | export const amountOptions = [
12 | {
13 | value: "1",
14 | label: "1 Photo",
15 | },
16 | {
17 | value: "2",
18 | label: "2 Photos",
19 | },
20 | {
21 | value: "3",
22 | label: "3 Photos",
23 | },
24 | {
25 | value: "4",
26 | label: "4 Photos",
27 | },
28 | {
29 | value: "5",
30 | label: "5 Photos",
31 | },
32 | ];
33 |
34 | export const resolutionOptions = [
35 | {
36 | value: "256x256",
37 | label: "256x256",
38 | },
39 | {
40 | value: "512x512",
41 | label: "512x512",
42 | },
43 | {
44 | value: "1024x1024",
45 | label: "1024x1024",
46 | },
47 | ];
48 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/image/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { zodResolver } from "@hookform/resolvers/zod";
4 | import axios from "axios";
5 | import { Download, ImageIcon } from "lucide-react";
6 | import { useRouter } from "next/navigation";
7 | import { useState } from "react";
8 | import { useForm } from "react-hook-form";
9 | import * as z from "zod";
10 |
11 | import { Empty } from "@/components/empty";
12 | import { Heading } from "@/components/heading";
13 | import { Loader } from "@/components/loader";
14 | import { Button } from "@/components/ui/button";
15 | import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
16 | import { Input } from "@/components/ui/input";
17 | import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
18 |
19 | import { Card, CardFooter } from "@/components/ui/card";
20 | import useProModal from "@/hooks/use-pro-modal";
21 | import Image from "next/image";
22 | import { toast } from "react-hot-toast";
23 | import { amountOptions, formSchema, resolutionOptions } from "./constants";
24 |
25 | const ImagePage = () => {
26 | const router = useRouter();
27 | const proModal = useProModal();
28 | const [images, setImages] = useState([]);
29 |
30 | const form = useForm>({
31 | resolver: zodResolver(formSchema),
32 | defaultValues: {
33 | prompt: "",
34 | amount: "1",
35 | resolution: "512x512",
36 | },
37 | });
38 |
39 | const isLoading = form.formState.isSubmitting;
40 |
41 | const onSubmit = async (values: z.infer) => {
42 | console.log(values);
43 | try {
44 | setImages([]);
45 | const response = await axios.post("/api/image", values);
46 |
47 | const urls = response.data.map((image: { url: string }) => image.url);
48 | setImages(urls);
49 |
50 | form.reset();
51 | } catch (error: any) {
52 | console.log(error);
53 | if (error?.response?.status === 403) {
54 | proModal.onOpen();
55 | } else {
56 | toast.error("Something went wrong.");
57 | }
58 | } finally {
59 | router.refresh();
60 | }
61 | };
62 |
63 | return (
64 |
65 |
72 |
73 |
74 |
142 |
143 |
144 |
145 | {isLoading && (
146 |
147 |
148 |
149 | )}
150 | {images.length === 0 && !isLoading &&
}
151 |
152 | {images.map((image, index) => (
153 |
154 |
155 |
156 |
157 |
158 |
162 |
163 |
164 | ))}
165 |
166 |
167 |
168 |
169 | );
170 | };
171 |
172 | export default ImagePage;
173 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/music/constants.tsx:
--------------------------------------------------------------------------------
1 | import * as z from "zod";
2 |
3 | export const formSchema = z.object({
4 | prompt: z.string().min(1, {
5 | message: "Prompt is required.",
6 | }),
7 | });
8 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/music/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { zodResolver } from "@hookform/resolvers/zod";
4 | import axios from "axios";
5 | import { Music } from "lucide-react";
6 | import { useRouter } from "next/navigation";
7 | import { useState } from "react";
8 | import { useForm } from "react-hook-form";
9 | import * as z from "zod";
10 |
11 | import { Empty } from "@/components/empty";
12 | import { Heading } from "@/components/heading";
13 | import { Loader } from "@/components/loader";
14 | import { Button } from "@/components/ui/button";
15 | import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
16 | import { Input } from "@/components/ui/input";
17 |
18 | import useProModal from "@/hooks/use-pro-modal";
19 | import { toast } from "react-hot-toast";
20 | import { formSchema } from "./constants";
21 |
22 | const MusicPage = () => {
23 | const router = useRouter();
24 | const proModal = useProModal();
25 | const [music, setMusic] = useState("");
26 |
27 | const form = useForm>({
28 | resolver: zodResolver(formSchema),
29 | defaultValues: {
30 | prompt: "",
31 | },
32 | });
33 |
34 | const isLoading = form.formState.isSubmitting;
35 |
36 | const onSubmit = async (values: z.infer) => {
37 | console.log(values);
38 | try {
39 | setMusic("");
40 | const response = await axios.post("/api/music");
41 | setMusic(response.data.audio);
42 |
43 | form.reset();
44 | } catch (error: any) {
45 | console.log(error);
46 | if (error?.response?.status === 403) {
47 | proModal.onOpen();
48 | } else {
49 | toast.error("Something went wrong.");
50 | }
51 | } finally {
52 | router.refresh();
53 | }
54 | };
55 |
56 | return (
57 |
58 |
65 |
66 |
67 |
91 |
92 |
93 |
94 | {isLoading && (
95 |
96 |
97 |
98 | )}
99 | {!music && !isLoading &&
}
100 | {music && (
101 |
104 | )}
105 |
106 |
107 |
108 | );
109 | };
110 |
111 | export default MusicPage;
112 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/settings/page.tsx:
--------------------------------------------------------------------------------
1 | import { Heading } from "@/components/heading";
2 | import { SubscriptionButton } from "@/components/subscription-button";
3 | import { checkSubscription } from "@/lib/subscription";
4 | import { Settings } from "lucide-react";
5 |
6 | const SettingsPage = async () => {
7 | const isPro = await checkSubscription();
8 |
9 | return (
10 |
11 |
12 |
13 |
14 | {isPro ? "You are currently on a pro plan." : "You are currently on a free plan."}
15 |
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default SettingsPage;
23 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/video/constants.tsx:
--------------------------------------------------------------------------------
1 | import * as z from "zod";
2 |
3 | export const formSchema = z.object({
4 | prompt: z.string().min(1, {
5 | message: "Prompt is required.",
6 | }),
7 | });
8 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/video/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { zodResolver } from "@hookform/resolvers/zod";
4 | import axios from "axios";
5 | import { Video } from "lucide-react";
6 | import { useRouter } from "next/navigation";
7 | import { useState } from "react";
8 | import { useForm } from "react-hook-form";
9 | import * as z from "zod";
10 |
11 | import { Empty } from "@/components/empty";
12 | import { Heading } from "@/components/heading";
13 | import { Loader } from "@/components/loader";
14 | import { Button } from "@/components/ui/button";
15 | import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
16 | import { Input } from "@/components/ui/input";
17 |
18 | import useProModal from "@/hooks/use-pro-modal";
19 | import { toast } from "react-hot-toast";
20 | import { formSchema } from "./constants";
21 |
22 | const VideoPage = () => {
23 | const router = useRouter();
24 | const proModal = useProModal();
25 | const [video, setVideo] = useState("");
26 |
27 | const form = useForm>({
28 | resolver: zodResolver(formSchema),
29 | defaultValues: {
30 | prompt: "",
31 | },
32 | });
33 |
34 | const isLoading = form.formState.isSubmitting;
35 |
36 | const onSubmit = async (values: z.infer) => {
37 | console.log(values);
38 | try {
39 | setVideo("");
40 | const response = await axios.post("/api/music");
41 | setVideo(response.data.audio);
42 |
43 | form.reset();
44 | } catch (error: any) {
45 | console.log(error);
46 | if (error?.response?.status === 403) {
47 | proModal.onOpen();
48 | } else {
49 | toast.error("Something went wrong.");
50 | }
51 | } finally {
52 | router.refresh();
53 | }
54 | };
55 |
56 | return (
57 |
58 |
65 |
66 |
67 |
91 |
92 |
93 |
94 | {isLoading && (
95 |
96 |
97 |
98 | )}
99 | {!video && !isLoading &&
}
100 | {video && (
101 |
104 | )}
105 |
106 |
107 |
108 | );
109 | };
110 |
111 | export default VideoPage;
112 |
--------------------------------------------------------------------------------
/app/(dashboard)/layout.tsx:
--------------------------------------------------------------------------------
1 | import Navbar from "@/components/navbar";
2 | import Sidebar from "@/components/sidebar";
3 | import { getApiLimitCount } from "@/lib/api-limit";
4 | import { checkSubscription } from "@/lib/subscription";
5 |
6 | const DashboardLayout = async ({ children }: { children: React.ReactNode }) => {
7 | const apiLimitCount = await getApiLimitCount();
8 | const isPro = await checkSubscription();
9 |
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 | {children}
18 |
19 |
20 | );
21 | };
22 |
23 | export default DashboardLayout;
24 |
--------------------------------------------------------------------------------
/app/(landing)/layout.tsx:
--------------------------------------------------------------------------------
1 | const LandingLayout = ({ children }: { children: React.ReactNode }) => {
2 | return (
3 |
4 | {children}
5 |
6 | );
7 | };
8 |
9 | export default LandingLayout;
10 |
--------------------------------------------------------------------------------
/app/(landing)/page.tsx:
--------------------------------------------------------------------------------
1 | import LandingContent from "@/components/landing-content";
2 | import { LandingHero } from "@/components/landing-hero";
3 | import { LandingNabvbar } from "@/components/landing-navbar";
4 |
5 | function LandingPage() {
6 | return (
7 |
8 |
9 |
10 |
11 |
12 | );
13 | }
14 |
15 | export default LandingPage;
16 |
--------------------------------------------------------------------------------
/app/api/code/route.ts:
--------------------------------------------------------------------------------
1 | import { checkApiLimit, increaseApiLimit } from "@/lib/api-limit";
2 | import { checkSubscription } from "@/lib/subscription";
3 | import { auth } from "@clerk/nextjs";
4 | import { NextResponse } from "next/server";
5 | import { ChatCompletionRequestMessage, Configuration, OpenAIApi } from "openai";
6 |
7 | const configuration = new Configuration({
8 | apiKey: process.env.OPENAI_API_KEY,
9 | });
10 |
11 | const openAi = new OpenAIApi(configuration);
12 |
13 | const instructionMessage: ChatCompletionRequestMessage = {
14 | role: "system",
15 | content: "You are a code generator. You must answer only in markdown code snippets. Use code comments for explanations.",
16 | };
17 |
18 | export async function POST(req: Request) {
19 | try {
20 | const { userId } = auth();
21 | const body = await req.json();
22 | const { messages } = body;
23 |
24 | if (!userId) {
25 | return new NextResponse("Unauthorized", { status: 401 });
26 | }
27 |
28 | if (!configuration) {
29 | return new NextResponse("OpenAI API Key not configured", { status: 500 });
30 | }
31 |
32 | if (!messages) {
33 | return new NextResponse("Missing messages", { status: 400 });
34 | }
35 |
36 | const isAllowed = await checkApiLimit();
37 | const isPro = await checkSubscription();
38 |
39 | if (!isAllowed && !isPro) {
40 | return new NextResponse("API Limit Exceeded", { status: 403 });
41 | }
42 |
43 | const response = await openAi.createChatCompletion({
44 | model: "gpt-3.5-turbo",
45 | messages: [instructionMessage, ...messages],
46 | });
47 |
48 | if (!isPro) {
49 | await increaseApiLimit();
50 | }
51 |
52 | return NextResponse.json(response.data.choices[0].message, { status: 200 });
53 | } catch (error) {
54 | console.log("[CODE_ERROR]", error);
55 | return new NextResponse("Internal Server Error", { status: 500 });
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/api/conversation/route.ts:
--------------------------------------------------------------------------------
1 | import { auth } from "@clerk/nextjs";
2 | import { NextResponse } from "next/server";
3 | import { Configuration, OpenAIApi } from "openai";
4 |
5 | import { checkApiLimit, increaseApiLimit } from "@/lib/api-limit";
6 | import { checkSubscription } from "@/lib/subscription";
7 |
8 | const configuration = new Configuration({
9 | apiKey: process.env.OPENAI_API_KEY,
10 | });
11 |
12 | const openAi = new OpenAIApi(configuration);
13 |
14 | export async function POST(req: Request) {
15 | try {
16 | const { userId } = auth();
17 | const body = await req.json();
18 | const { messages } = body;
19 |
20 | if (!userId) {
21 | return new NextResponse("Unauthorized", { status: 401 });
22 | }
23 |
24 | if (!configuration) {
25 | return new NextResponse("OpenAI API Key not configured", { status: 500 });
26 | }
27 |
28 | if (!messages) {
29 | return new NextResponse("Missing messages", { status: 400 });
30 | }
31 |
32 | const isAllowed = await checkApiLimit();
33 | const isPro = await checkSubscription();
34 |
35 | if (!isAllowed && !isPro) {
36 | return new NextResponse("API Limit Exceeded", { status: 403 });
37 | }
38 |
39 | const response = await openAi.createChatCompletion({
40 | model: "gpt-3.5-turbo",
41 | messages,
42 | });
43 |
44 | if (!isPro) {
45 | await increaseApiLimit();
46 | }
47 |
48 | return NextResponse.json(response.data.choices[0].message, { status: 200 });
49 | } catch (error) {
50 | console.log("[CONVERSATION_ERROR]", error);
51 | return new NextResponse("Internal Server Error", { status: 500 });
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/api/image/route.ts:
--------------------------------------------------------------------------------
1 | import { checkApiLimit, increaseApiLimit } from "@/lib/api-limit";
2 | import { checkSubscription } from "@/lib/subscription";
3 | import { auth } from "@clerk/nextjs";
4 | import { NextResponse } from "next/server";
5 | import { Configuration, OpenAIApi } from "openai";
6 |
7 | const configuration = new Configuration({
8 | apiKey: process.env.OPENAI_API_KEY,
9 | });
10 |
11 | const openAi = new OpenAIApi(configuration);
12 |
13 | export async function POST(req: Request) {
14 | try {
15 | const { userId } = auth();
16 | const body = await req.json();
17 | const { prompt, amount = 1, resolution = "512x512" } = body;
18 |
19 | if (!userId) {
20 | return new NextResponse("Unauthorized", { status: 401 });
21 | }
22 |
23 | if (!configuration) {
24 | return new NextResponse("OpenAI API Key not configured", { status: 500 });
25 | }
26 |
27 | if (!prompt) {
28 | return new NextResponse("Missing prompt", { status: 400 });
29 | }
30 |
31 | if (!amount) {
32 | return new NextResponse("Missing amount", { status: 400 });
33 | }
34 |
35 | if (!resolution) {
36 | return new NextResponse("Missing resolution", { status: 400 });
37 | }
38 |
39 | const isAllowed = await checkApiLimit();
40 | const isPro = await checkSubscription();
41 |
42 | if (!isAllowed && !isPro) {
43 | return new NextResponse("API Limit Exceeded", { status: 403 });
44 | }
45 |
46 | const response = await openAi.createImage({
47 | prompt,
48 | n: parseInt(amount, 10),
49 | size: resolution,
50 | });
51 |
52 | if (!isPro) {
53 | await increaseApiLimit();
54 | }
55 |
56 | return NextResponse.json(response.data.data, { status: 200 });
57 | } catch (error) {
58 | console.log("[CONVERSATION_ERROR]", error);
59 | return new NextResponse("Internal Server Error", { status: 500 });
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/api/music/route.ts:
--------------------------------------------------------------------------------
1 | import { checkApiLimit, increaseApiLimit } from "@/lib/api-limit";
2 | import { checkSubscription } from "@/lib/subscription";
3 | import { auth } from "@clerk/nextjs";
4 | import { NextResponse } from "next/server";
5 | import Replicate from "replicate";
6 |
7 | const replicate = new Replicate({
8 | auth: process.env.REPLICATE_API_TOKEN!,
9 | });
10 |
11 | export async function POST(req: Request) {
12 | try {
13 | const { userId } = auth();
14 | const body = await req.json();
15 | const { prompt } = body;
16 |
17 | if (!userId) {
18 | return new NextResponse("Unauthorized", { status: 401 });
19 | }
20 |
21 | if (!prompt) {
22 | return new NextResponse("Prompt is required", { status: 400 });
23 | }
24 |
25 | const isAllowed = await checkApiLimit();
26 | const isPro = await checkSubscription();
27 |
28 | if (!isAllowed && !isPro) {
29 | return new NextResponse("API Limit Exceeded", { status: 403 });
30 | }
31 |
32 | const response = await replicate.run("riffusion/riffusion:8cf61ea6c56afd61d8f5b9ffd14d7c216c0a93844ce2d82ac1c9ecc9c7f24e05", {
33 | input: {
34 | prompt_a: prompt,
35 | },
36 | });
37 |
38 | if (!isPro) {
39 | await increaseApiLimit();
40 | }
41 |
42 | return NextResponse.json(response, { status: 200 });
43 | } catch (error) {
44 | console.log("[MUSIC_ERROR]", error);
45 | return new NextResponse("Internal Server Error", { status: 500 });
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/api/stripe/route.ts:
--------------------------------------------------------------------------------
1 | import { auth, currentUser } from "@clerk/nextjs";
2 | import { NextResponse } from "next/server";
3 |
4 | import prismadb from "@/lib/prismadb";
5 | import { stripe } from "@/lib/stripe";
6 | import { absoluteUrl } from "@/lib/utils";
7 |
8 | const settingsUrl = absoluteUrl("/settings");
9 |
10 | export async function GET() {
11 | try {
12 | const { userId } = auth();
13 | const user = await currentUser();
14 |
15 | if (!userId || !user) {
16 | return new NextResponse("Unauthorized", { status: 401 });
17 | }
18 |
19 | const userSubscription = await prismadb.userSubscription.findUnique({
20 | where: {
21 | userId,
22 | },
23 | });
24 |
25 | if (userSubscription && userSubscription.stripeCustomerId) {
26 | const stripeSession = await stripe.billingPortal.sessions.create({
27 | customer: userSubscription.stripeCustomerId,
28 | return_url: settingsUrl,
29 | });
30 |
31 | return new NextResponse(JSON.stringify({ url: stripeSession.url }), { status: 200 });
32 | }
33 |
34 | const stripeSession = await stripe.checkout.sessions.create({
35 | success_url: settingsUrl,
36 | cancel_url: settingsUrl,
37 | payment_method_types: ["card"],
38 | mode: "subscription",
39 | billing_address_collection: "auto",
40 | customer_email: user.emailAddresses[0].emailAddress,
41 | line_items: [
42 | {
43 | price_data: {
44 | currency: "USD",
45 | product_data: {
46 | name: "Prometheus Pro",
47 | description: "Prometheus Pro",
48 | },
49 | unit_amount: 2000,
50 | recurring: {
51 | interval: "month",
52 | },
53 | },
54 | quantity: 1,
55 | },
56 | ],
57 | metadata: {
58 | userId,
59 | },
60 | });
61 |
62 | return new NextResponse(JSON.stringify({ url: stripeSession.url }), { status: 200 });
63 | } catch (error) {
64 | console.log("[STRIPE_ERROR]", error);
65 | return new NextResponse("Internal Server Error", { status: 500 });
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/api/video/route.ts:
--------------------------------------------------------------------------------
1 | import { checkApiLimit, increaseApiLimit } from "@/lib/api-limit";
2 | import { checkSubscription } from "@/lib/subscription";
3 | import { auth } from "@clerk/nextjs";
4 | import { NextResponse } from "next/server";
5 | import Replicate from "replicate";
6 |
7 | const replicate = new Replicate({
8 | auth: process.env.REPLICATE_API_TOKEN!,
9 | });
10 |
11 | export async function POST(req: Request) {
12 | try {
13 | const { userId } = auth();
14 | const body = await req.json();
15 | const { prompt } = body;
16 |
17 | if (!userId) {
18 | return new NextResponse("Unauthorized", { status: 401 });
19 | }
20 |
21 | if (!prompt) {
22 | return new NextResponse("Prompt is required", { status: 400 });
23 | }
24 |
25 | const isAllowed = await checkApiLimit();
26 | const isPro = await checkSubscription();
27 |
28 | if (!isAllowed && !isPro) {
29 | return new NextResponse("API Limit Exceeded", { status: 403 });
30 | }
31 |
32 | const response = await replicate.run("anotherjesse/zeroscope-v2-xl:9f747673945c62801b13b84701c783929c0ee784e4748ec062204894dda1a351", {
33 | input: {
34 | prompt,
35 | },
36 | });
37 |
38 | if (!isPro) {
39 | await increaseApiLimit();
40 | }
41 |
42 | return NextResponse.json(response, { status: 200 });
43 | } catch (error) {
44 | console.log("[VIDEO_ERROR]", error);
45 | return new NextResponse("Internal Server Error", { status: 500 });
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/api/webhook/route.ts:
--------------------------------------------------------------------------------
1 | import { headers } from "next/headers";
2 | import Stripe from "stripe";
3 |
4 | import prismadb from "@/lib/prismadb";
5 | import { stripe } from "@/lib/stripe";
6 | import { NextResponse } from "next/server";
7 |
8 | export async function POST(req: Request) {
9 | const body = await req.text();
10 | const signature = headers().get("Stripe-Signature") as string;
11 |
12 | let event: Stripe.Event;
13 |
14 | try {
15 | event = stripe.webhooks.constructEvent(body, signature, process.env.STRIPE_WEBHOOK_SECRET!);
16 | } catch (error: any) {
17 | return new NextResponse(`Webhook Error: ${error.message}`, { status: 400 });
18 | }
19 |
20 | const session = event.data.object as Stripe.Checkout.Session;
21 |
22 | if (event.type === "checkout.session.completed") {
23 | const subscription = await stripe.subscriptions.retrieve(session.subscription as string);
24 |
25 | if (!session?.metadata?.userId) {
26 | return new NextResponse("Unauthorized", { status: 401 });
27 | }
28 |
29 | await prismadb.userSubscription.create({
30 | data: {
31 | userId: session.metadata.userId,
32 | stripeSubscriptionId: subscription.id,
33 | stripeCustomerId: subscription.customer as string,
34 | stripePriceId: subscription.items.data[0].price.id,
35 | stripeCurrentPeriodEnd: new Date(subscription.current_period_end * 1000),
36 | },
37 | });
38 | }
39 |
40 | if (event.type === "invoice.payment_succeeded") {
41 | const subscription = await stripe.subscriptions.retrieve(session.subscription as string);
42 |
43 | await prismadb.userSubscription.update({
44 | where: {
45 | stripeSubscriptionId: subscription.id,
46 | },
47 | data: {
48 | stripePriceId: subscription.items.data[0].price.id,
49 | stripeCurrentPeriodEnd: new Date(subscription.current_period_end * 1000),
50 | },
51 | });
52 | }
53 |
54 | return new NextResponse("OK", { status: 200 });
55 | }
56 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayusshrathore/ai-saas/623dba771e012fb2923c0ef6ded4127bed09181e/app/favicon.ico
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | html,
6 | body,
7 | :root {
8 | height: 100%;
9 | }
10 |
11 | @layer base {
12 | :root {
13 | --background: 0 0% 100%;
14 | --foreground: 222.2 84% 4.9%;
15 |
16 | --muted: 210 40% 96.1%;
17 | --muted-foreground: 215.4 16.3% 46.9%;
18 |
19 | --popover: 0 0% 100%;
20 | --popover-foreground: 222.2 84% 4.9%;
21 |
22 | --card: 0 0% 100%;
23 | --card-foreground: 222.2 84% 4.9%;
24 |
25 | --border: 214.3 31.8% 91.4%;
26 | --input: 214.3 31.8% 91.4%;
27 |
28 | --primary: 248 90% 66%;
29 | --primary-foreground: 210 40% 98%;
30 |
31 | --secondary: 210 40% 96.1%;
32 | --secondary-foreground: 222.2 47.4% 11.2%;
33 |
34 | --accent: 210 40% 96.1%;
35 | --accent-foreground: 222.2 47.4% 11.2%;
36 |
37 | --destructive: 0 84.2% 60.2%;
38 | --destructive-foreground: 210 40% 98%;
39 |
40 | --ring: 215 20.2% 65.1%;
41 |
42 | --radius: 0.5rem;
43 | }
44 |
45 | .dark {
46 | --background: 222.2 84% 4.9%;
47 | --foreground: 210 40% 98%;
48 |
49 | --muted: 217.2 32.6% 17.5%;
50 | --muted-foreground: 215 20.2% 65.1%;
51 |
52 | --popover: 222.2 84% 4.9%;
53 | --popover-foreground: 210 40% 98%;
54 |
55 | --card: 222.2 84% 4.9%;
56 | --card-foreground: 210 40% 98%;
57 |
58 | --border: 217.2 32.6% 17.5%;
59 | --input: 217.2 32.6% 17.5%;
60 |
61 | --primary: 210 40% 98%;
62 | --primary-foreground: 222.2 47.4% 11.2%;
63 |
64 | --secondary: 217.2 32.6% 17.5%;
65 | --secondary-foreground: 210 40% 98%;
66 |
67 | --accent: 217.2 32.6% 17.5%;
68 | --accent-foreground: 210 40% 98%;
69 |
70 | --destructive: 0 62.8% 30.6%;
71 | --destructive-foreground: 0 85.7% 97.3%;
72 |
73 | --ring: 217.2 32.6% 17.5%;
74 | }
75 | }
76 |
77 | @layer base {
78 | * {
79 | @apply border-border;
80 | }
81 | body {
82 | @apply bg-background text-foreground;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import { ClerkProvider } from "@clerk/nextjs";
2 | import type { Metadata } from "next";
3 | import { Inter } from "next/font/google";
4 |
5 | import CrispProvider from "@/components/crisp-provider";
6 | import { ModalProvider } from "@/components/modal-provider";
7 | import { ToasterProvider } from "@/components/toaster-provider";
8 | import "./globals.css";
9 |
10 | const inter = Inter({ subsets: ["latin"] });
11 |
12 | export const metadata: Metadata = {
13 | title: "Prometheus AI",
14 | description: "An AI platform.",
15 | };
16 |
17 | export default function RootLayout({ children }: { children: React.ReactNode }) {
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 | {children}
26 |
27 |
28 |
29 | );
30 | }
31 |
--------------------------------------------------------------------------------
/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "default",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "app/globals.css",
9 | "baseColor": "slate",
10 | "cssVariables": true
11 | },
12 | "aliases": {
13 | "components": "@/components",
14 | "utils": "@/lib/utils"
15 | }
16 | }
--------------------------------------------------------------------------------
/components/bot-avatar.tsx:
--------------------------------------------------------------------------------
1 | import { Avatar, AvatarImage } from "./ui/avatar";
2 |
3 | export const BotAvatar = () => {
4 | return (
5 |
6 |
7 |
8 | );
9 | };
10 |
--------------------------------------------------------------------------------
/components/crisp-chat.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Crisp } from "crisp-sdk-web";
4 | import { useEffect } from "react";
5 |
6 | export const CrispChat = () => {
7 | useEffect(() => {
8 | Crisp.configure(process.env.CRISP_WEBSITE_ID!);
9 | }, []);
10 |
11 | return null;
12 | };
13 |
--------------------------------------------------------------------------------
/components/crisp-provider.tsx:
--------------------------------------------------------------------------------
1 | import { CrispChat } from "./crisp-chat";
2 |
3 | export const CrispProvider = () => {
4 | return ;
5 | };
6 |
7 | export default CrispProvider;
8 |
--------------------------------------------------------------------------------
/components/empty.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 |
3 | interface EmptyProps {
4 | label: string;
5 | }
6 |
7 | export const Empty = ({ label }: EmptyProps) => {
8 | return (
9 |
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/components/free-counter.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { FC, useEffect, useState } from "react";
4 |
5 | import { MAX_FREE_COUNTS } from "@/constants";
6 | import useProModal from "@/hooks/use-pro-modal";
7 | import { Zap } from "lucide-react";
8 | import { Button } from "./ui/button";
9 | import { Card, CardContent } from "./ui/card";
10 | import { Progress } from "./ui/progress";
11 |
12 | interface FreeCounterProps {
13 | apiLimitCount: number;
14 | isPro: boolean;
15 | }
16 |
17 | export const FreeCounter: FC = ({ apiLimitCount = 0, isPro = false }) => {
18 | const [mounted, setMounted] = useState(false);
19 | const proModal = useProModal();
20 |
21 | useEffect(() => {
22 | setMounted(true);
23 | }, []);
24 |
25 | if (!mounted) {
26 | return null;
27 | }
28 |
29 | if (isPro) {
30 | return null;
31 | }
32 |
33 | return (
34 |
35 |
36 |
37 |
38 |
39 | {apiLimitCount} / {MAX_FREE_COUNTS} Free Generations
40 |
41 |
42 |
43 |
46 |
47 |
48 |
49 | );
50 | };
51 |
52 | export default FreeCounter;
53 |
--------------------------------------------------------------------------------
/components/heading.tsx:
--------------------------------------------------------------------------------
1 | import { LucideIcon } from "lucide-react";
2 |
3 | import { cn } from "@/lib/utils";
4 |
5 | interface HeadingProps {
6 | title: string;
7 | description: string;
8 | icon: LucideIcon;
9 | iconColor?: string;
10 | bgColor?: string;
11 | }
12 |
13 | export const Heading = ({
14 | title,
15 | description,
16 | icon: Icon,
17 | iconColor,
18 | bgColor,
19 | }: HeadingProps) => {
20 | return (
21 |
22 |
23 |
24 |
25 |
26 |
{title}
27 |
{description}
28 |
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/components/landing-content.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
4 |
5 | const testimonials = [
6 | {
7 | name: "John Doe",
8 | avatar: "A",
9 | title: "Software Engineer",
10 | description: "This is the best application I've ever used!",
11 | },
12 | {
13 | name: "John Doe",
14 | avatar: "A",
15 | title: "Software Engineer",
16 | description: "This is the best application I've ever used!",
17 | },
18 | {
19 | name: "John Doe",
20 | avatar: "A",
21 | title: "Software Engineer",
22 | description: "This is the best application I've ever used!",
23 | },
24 | {
25 | name: "John Doe",
26 | avatar: "A",
27 | title: "Software Engineer",
28 | description: "This is the best application I've ever used!",
29 | },
30 | ];
31 |
32 | export const LandingContent = () => {
33 | return (
34 |
35 |
Testimonials
36 |
37 | {testimonials.map((item) => (
38 |
39 |
40 |
41 |
42 |
{item.name}
43 |
{item.title}
44 |
45 |
46 | {item.description}
47 |
48 |
49 | ))}
50 |
51 |
52 | );
53 | };
54 |
55 | export default LandingContent;
56 |
--------------------------------------------------------------------------------
/components/landing-hero.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useAuth } from "@clerk/nextjs";
4 | import Link from "next/link";
5 | import TypewriterComponent from "typewriter-effect";
6 |
7 | import { Button } from "./ui/button";
8 |
9 | export const LandingHero = () => {
10 | const { isSignedIn } = useAuth();
11 | return (
12 |
13 |
14 |
The Best AI Tool for
15 |
16 |
23 |
24 |
25 |
Create content using the power of AI.
26 |
27 |
28 |
31 |
32 |
33 |
No credit card required. Cancel anytime.
34 |
35 | );
36 | };
37 |
--------------------------------------------------------------------------------
/components/landing-navbar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useAuth } from "@clerk/nextjs";
4 | import { Montserrat } from "next/font/google";
5 | import Image from "next/image";
6 | import Link from "next/link";
7 |
8 | import { cn } from "@/lib/utils";
9 | import { Button } from "./ui/button";
10 |
11 | const font = Montserrat({
12 | weight: "600",
13 | subsets: ["latin"],
14 | });
15 |
16 | export const LandingNabvbar = () => {
17 | const { isSignedIn } = useAuth();
18 |
19 | return (
20 |
35 | );
36 | };
37 |
--------------------------------------------------------------------------------
/components/loader.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 |
3 | export const Loader = () => {
4 | return (
5 |
6 |
7 |
8 |
9 |
Prometheus is thinking...
10 |
11 | );
12 | };
13 |
--------------------------------------------------------------------------------
/components/mobile-sidebar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Menu } from "lucide-react";
4 | import { useEffect, useState } from "react";
5 |
6 | import Sidebar from "./sidebar";
7 | import { Button } from "./ui/button";
8 | import { Sheet, SheetContent, SheetTrigger } from "./ui/sheet";
9 |
10 | const MobileSidebar = ({ apiLimitCount = 0, isPro = false }: { apiLimitCount: number; isPro: boolean }) => {
11 | const [isMounted, setIsMounted] = useState(false);
12 |
13 | useEffect(() => {
14 | setIsMounted(true);
15 | }, []);
16 |
17 | if (!isMounted) {
18 | return null;
19 | }
20 |
21 | return (
22 |
23 |
24 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
35 | export default MobileSidebar;
36 |
--------------------------------------------------------------------------------
/components/modal-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect, useState } from "react";
4 |
5 | import ProModal from "./pro-modal";
6 |
7 | export const ModalProvider = () => {
8 | const [mounted, setMounted] = useState(false);
9 |
10 | useEffect(() => {
11 | setMounted(true);
12 | }, []);
13 |
14 | if (!mounted) {
15 | return null;
16 | }
17 |
18 | return (
19 | <>
20 |
21 | >
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/components/navbar.tsx:
--------------------------------------------------------------------------------
1 | import { UserButton } from "@clerk/nextjs";
2 |
3 | import { getApiLimitCount } from "@/lib/api-limit";
4 | import { checkSubscription } from "@/lib/subscription";
5 | import MobileSidebar from "./mobile-sidebar";
6 |
7 | const Navbar = async () => {
8 | const apiLimitCount = await getApiLimitCount();
9 | const isPro = await checkSubscription();
10 |
11 | return (
12 |
18 | );
19 | };
20 |
21 | export default Navbar;
22 |
--------------------------------------------------------------------------------
/components/pro-modal.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import axios from "axios";
4 | import { useState } from "react";
5 |
6 | import useProModal from "@/hooks/use-pro-modal";
7 | import { cn } from "@/lib/utils";
8 | import { Check, Code, ImageIcon, MessageSquare, Music, VideoIcon, Zap } from "lucide-react";
9 | import { toast } from "react-hot-toast";
10 | import { Badge } from "./ui/badge";
11 | import { Button } from "./ui/button";
12 | import { Card } from "./ui/card";
13 | import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "./ui/dialog";
14 |
15 | export const ProModal = () => {
16 | const proModal = useProModal();
17 | const [loading, setLoading] = useState(false);
18 |
19 | const tools = [
20 | {
21 | label: "Conversation",
22 | icon: MessageSquare,
23 | color: "text-violet-500",
24 | bgColor: "bg-violet-500/10",
25 | },
26 | {
27 | label: "Music Generation",
28 | icon: Music,
29 | color: "text-emerald-500",
30 | bgColor: "bg-emerald-500/10",
31 | },
32 | {
33 | label: "Image Generation",
34 | icon: ImageIcon,
35 | color: "text-pink-700",
36 | bgColor: "bg-pink-700/10",
37 | },
38 | {
39 | label: "Video Generation",
40 | icon: VideoIcon,
41 | color: "text-orange-700",
42 | bgColor: "bg-orange-700/10",
43 | },
44 | {
45 | label: "Code Generation",
46 | icon: Code,
47 | color: "text-green-700",
48 | bgColor: "bg-green-700/10",
49 | },
50 | ];
51 |
52 | const onSubscribe = async () => {
53 | try {
54 | setLoading(true);
55 | const response = await axios.get("/api/stripe");
56 |
57 | window.location.href = response.data.url;
58 | } catch (error) {
59 | console.log("[STRIPE_CLIENT_ERROR]", error);
60 | toast.error("Something went wrong.");
61 | } finally {
62 | setLoading(false);
63 | }
64 | };
65 |
66 | return (
67 |
99 | );
100 | };
101 |
102 | export default ProModal;
103 |
--------------------------------------------------------------------------------
/components/sidebar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Code, ImageIcon, LayoutDashboard, MessageSquare, Music, Settings, VideoIcon } from "lucide-react";
4 | import { Montserrat } from "next/font/google";
5 | import Image from "next/image";
6 | import Link from "next/link";
7 | import { usePathname } from "next/navigation";
8 | import { FC } from "react";
9 |
10 | import { cn } from "@/lib/utils";
11 | import FreeCounter from "./free-counter";
12 |
13 | const montserrat = Montserrat({ weight: "600", subsets: ["latin"] });
14 |
15 | const routes = [
16 | {
17 | label: "Dashboard",
18 | icon: LayoutDashboard,
19 | href: "/dashboard",
20 | color: "text-sky-500",
21 | },
22 | {
23 | label: "Conversation",
24 | icon: MessageSquare,
25 | href: "/conversation",
26 | color: "text-violet-500",
27 | },
28 | {
29 | label: "Image Generation",
30 | icon: ImageIcon,
31 | href: "/image",
32 | color: "text-pink-700",
33 | },
34 | {
35 | label: "Video Generation",
36 | icon: VideoIcon,
37 | href: "/video",
38 | color: "text-orange-700",
39 | },
40 | {
41 | label: "Music Generation",
42 | icon: Music,
43 | href: "/music",
44 | color: "text-emerald-500",
45 | },
46 | {
47 | label: "Code Generation",
48 | icon: Code,
49 | href: "/code",
50 | color: "text-green-700",
51 | },
52 | {
53 | label: "Settings",
54 | icon: Settings,
55 | href: "/settings",
56 | },
57 | ];
58 |
59 | interface SidebarProps {
60 | apiLimitCount: number;
61 | isPro: boolean;
62 | }
63 |
64 | const Sidebar: FC = ({ apiLimitCount = 0, isPro = false }) => {
65 | const pathname = usePathname();
66 |
67 | return (
68 |
69 |
70 |
71 |
72 |
73 |
74 |
Prometheus
75 |
76 |
77 | {routes.map((route) => (
78 |
86 |
87 |
88 | {route.label}
89 |
90 |
91 | ))}
92 |
93 |
94 |
95 |
96 | );
97 | };
98 |
99 | export default Sidebar;
100 |
--------------------------------------------------------------------------------
/components/subscription-button.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import axios from "axios";
4 | import { Zap } from "lucide-react";
5 | import { FC, useState } from "react";
6 | import { toast } from "react-hot-toast";
7 | import { Button } from "./ui/button";
8 |
9 | interface SubscriptionButtonProps {
10 | isPro: boolean;
11 | }
12 |
13 | export const SubscriptionButton: FC = ({ isPro = false }) => {
14 | const [loading, setLoading] = useState(false);
15 |
16 | const onClick = async () => {
17 | try {
18 | setLoading(true);
19 | const response = await axios.get("/api/stripe");
20 |
21 | window.location.href = response.data.url;
22 | } catch (error) {
23 | console.log("[BILLING_ERROR]", error);
24 | toast.error("Something went wrong.");
25 | } finally {
26 | setLoading(false);
27 | }
28 | };
29 |
30 | return (
31 |
35 | );
36 | };
37 |
--------------------------------------------------------------------------------
/components/toaster-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Toaster } from "react-hot-toast";
4 |
5 | export const ToasterProvider = () => {
6 | return ;
7 | };
8 |
--------------------------------------------------------------------------------
/components/ui/avatar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as AvatarPrimitive from "@radix-ui/react-avatar"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Avatar = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, ...props }, ref) => (
12 |
20 | ))
21 | Avatar.displayName = AvatarPrimitive.Root.displayName
22 |
23 | const AvatarImage = React.forwardRef<
24 | React.ElementRef,
25 | React.ComponentPropsWithoutRef
26 | >(({ className, ...props }, ref) => (
27 |
32 | ))
33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName
34 |
35 | const AvatarFallback = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, ...props }, ref) => (
39 |
47 | ))
48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
49 |
50 | export { Avatar, AvatarImage, AvatarFallback }
51 |
--------------------------------------------------------------------------------
/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import { cva, type VariantProps } from "class-variance-authority";
2 | import * as React from "react";
3 |
4 | import { cn } from "@/lib/utils";
5 |
6 | const badgeVariants = cva(
7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8 | {
9 | variants: {
10 | variant: {
11 | default: "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
12 | secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
13 | destructive: "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
14 | outline: "text-foreground",
15 | premium: "bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 text-primary-foreground border-0",
16 | },
17 | },
18 | defaultVariants: {
19 | variant: "default",
20 | },
21 | }
22 | );
23 |
24 | export interface BadgeProps extends React.HTMLAttributes, VariantProps {}
25 |
26 | function Badge({ className, variant, ...props }: BadgeProps) {
27 | return ;
28 | }
29 |
30 | export { Badge, badgeVariants };
31 |
--------------------------------------------------------------------------------
/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import { Slot } from "@radix-ui/react-slot";
2 | import { cva, type VariantProps } from "class-variance-authority";
3 | import * as React from "react";
4 |
5 | import { cn } from "@/lib/utils";
6 |
7 | const buttonVariants = cva(
8 | "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
9 | {
10 | variants: {
11 | variant: {
12 | default: "bg-primary text-primary-foreground hover:bg-primary/90",
13 | destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
14 | outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
15 | secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
16 | ghost: "hover:bg-accent hover:text-accent-foreground",
17 | link: "text-primary underline-offset-4 hover:underline",
18 | premium: "bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 text-white border-0",
19 | },
20 | size: {
21 | default: "h-10 px-4 py-2",
22 | sm: "h-9 rounded-md px-3",
23 | lg: "h-11 rounded-md px-8",
24 | icon: "h-10 w-10",
25 | },
26 | },
27 | defaultVariants: {
28 | variant: "default",
29 | size: "default",
30 | },
31 | }
32 | );
33 |
34 | export interface ButtonProps extends React.ButtonHTMLAttributes, VariantProps {
35 | asChild?: boolean;
36 | }
37 |
38 | const Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
39 | const Comp = asChild ? Slot : "button";
40 | return ;
41 | });
42 | Button.displayName = "Button";
43 |
44 | export { Button, buttonVariants };
45 |
--------------------------------------------------------------------------------
/components/ui/card.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | const Card = React.forwardRef<
6 | HTMLDivElement,
7 | React.HTMLAttributes
8 | >(({ className, ...props }, ref) => (
9 |
17 | ))
18 | Card.displayName = "Card"
19 |
20 | const CardHeader = React.forwardRef<
21 | HTMLDivElement,
22 | React.HTMLAttributes
23 | >(({ className, ...props }, ref) => (
24 |
29 | ))
30 | CardHeader.displayName = "CardHeader"
31 |
32 | const CardTitle = React.forwardRef<
33 | HTMLParagraphElement,
34 | React.HTMLAttributes
35 | >(({ className, ...props }, ref) => (
36 |
44 | ))
45 | CardTitle.displayName = "CardTitle"
46 |
47 | const CardDescription = React.forwardRef<
48 | HTMLParagraphElement,
49 | React.HTMLAttributes
50 | >(({ className, ...props }, ref) => (
51 |
56 | ))
57 | CardDescription.displayName = "CardDescription"
58 |
59 | const CardContent = React.forwardRef<
60 | HTMLDivElement,
61 | React.HTMLAttributes
62 | >(({ className, ...props }, ref) => (
63 |
64 | ))
65 | CardContent.displayName = "CardContent"
66 |
67 | const CardFooter = React.forwardRef<
68 | HTMLDivElement,
69 | React.HTMLAttributes
70 | >(({ className, ...props }, ref) => (
71 |
76 | ))
77 | CardFooter.displayName = "CardFooter"
78 |
79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
80 |
--------------------------------------------------------------------------------
/components/ui/dialog.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DialogPrimitive from "@radix-ui/react-dialog"
5 | import { X } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Dialog = DialogPrimitive.Root
10 |
11 | const DialogTrigger = DialogPrimitive.Trigger
12 |
13 | const DialogPortal = ({
14 | className,
15 | ...props
16 | }: DialogPrimitive.DialogPortalProps) => (
17 |
18 | )
19 | DialogPortal.displayName = DialogPrimitive.Portal.displayName
20 |
21 | const DialogOverlay = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef
24 | >(({ className, ...props }, ref) => (
25 |
33 | ))
34 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
35 |
36 | const DialogContent = React.forwardRef<
37 | React.ElementRef,
38 | React.ComponentPropsWithoutRef
39 | >(({ className, children, ...props }, ref) => (
40 |
41 |
42 |
50 | {children}
51 |
52 |
53 | Close
54 |
55 |
56 |
57 | ))
58 | DialogContent.displayName = DialogPrimitive.Content.displayName
59 |
60 | const DialogHeader = ({
61 | className,
62 | ...props
63 | }: React.HTMLAttributes) => (
64 |
71 | )
72 | DialogHeader.displayName = "DialogHeader"
73 |
74 | const DialogFooter = ({
75 | className,
76 | ...props
77 | }: React.HTMLAttributes) => (
78 |
85 | )
86 | DialogFooter.displayName = "DialogFooter"
87 |
88 | const DialogTitle = React.forwardRef<
89 | React.ElementRef,
90 | React.ComponentPropsWithoutRef
91 | >(({ className, ...props }, ref) => (
92 |
100 | ))
101 | DialogTitle.displayName = DialogPrimitive.Title.displayName
102 |
103 | const DialogDescription = React.forwardRef<
104 | React.ElementRef,
105 | React.ComponentPropsWithoutRef
106 | >(({ className, ...props }, ref) => (
107 |
112 | ))
113 | DialogDescription.displayName = DialogPrimitive.Description.displayName
114 |
115 | export {
116 | Dialog,
117 | DialogTrigger,
118 | DialogContent,
119 | DialogHeader,
120 | DialogFooter,
121 | DialogTitle,
122 | DialogDescription,
123 | }
124 |
--------------------------------------------------------------------------------
/components/ui/form.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import * as LabelPrimitive from "@radix-ui/react-label"
3 | import { Slot } from "@radix-ui/react-slot"
4 | import {
5 | Controller,
6 | ControllerProps,
7 | FieldPath,
8 | FieldValues,
9 | FormProvider,
10 | useFormContext,
11 | } from "react-hook-form"
12 |
13 | import { cn } from "@/lib/utils"
14 | import { Label } from "@/components/ui/label"
15 |
16 | const Form = FormProvider
17 |
18 | type FormFieldContextValue<
19 | TFieldValues extends FieldValues = FieldValues,
20 | TName extends FieldPath = FieldPath
21 | > = {
22 | name: TName
23 | }
24 |
25 | const FormFieldContext = React.createContext(
26 | {} as FormFieldContextValue
27 | )
28 |
29 | const FormField = <
30 | TFieldValues extends FieldValues = FieldValues,
31 | TName extends FieldPath = FieldPath
32 | >({
33 | ...props
34 | }: ControllerProps) => {
35 | return (
36 |
37 |
38 |
39 | )
40 | }
41 |
42 | const useFormField = () => {
43 | const fieldContext = React.useContext(FormFieldContext)
44 | const itemContext = React.useContext(FormItemContext)
45 | const { getFieldState, formState } = useFormContext()
46 |
47 | const fieldState = getFieldState(fieldContext.name, formState)
48 |
49 | if (!fieldContext) {
50 | throw new Error("useFormField should be used within ")
51 | }
52 |
53 | const { id } = itemContext
54 |
55 | return {
56 | id,
57 | name: fieldContext.name,
58 | formItemId: `${id}-form-item`,
59 | formDescriptionId: `${id}-form-item-description`,
60 | formMessageId: `${id}-form-item-message`,
61 | ...fieldState,
62 | }
63 | }
64 |
65 | type FormItemContextValue = {
66 | id: string
67 | }
68 |
69 | const FormItemContext = React.createContext(
70 | {} as FormItemContextValue
71 | )
72 |
73 | const FormItem = React.forwardRef<
74 | HTMLDivElement,
75 | React.HTMLAttributes
76 | >(({ className, ...props }, ref) => {
77 | const id = React.useId()
78 |
79 | return (
80 |
81 |
82 |
83 | )
84 | })
85 | FormItem.displayName = "FormItem"
86 |
87 | const FormLabel = React.forwardRef<
88 | React.ElementRef,
89 | React.ComponentPropsWithoutRef
90 | >(({ className, ...props }, ref) => {
91 | const { error, formItemId } = useFormField()
92 |
93 | return (
94 |
100 | )
101 | })
102 | FormLabel.displayName = "FormLabel"
103 |
104 | const FormControl = React.forwardRef<
105 | React.ElementRef,
106 | React.ComponentPropsWithoutRef
107 | >(({ ...props }, ref) => {
108 | const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
109 |
110 | return (
111 |
122 | )
123 | })
124 | FormControl.displayName = "FormControl"
125 |
126 | const FormDescription = React.forwardRef<
127 | HTMLParagraphElement,
128 | React.HTMLAttributes
129 | >(({ className, ...props }, ref) => {
130 | const { formDescriptionId } = useFormField()
131 |
132 | return (
133 |
139 | )
140 | })
141 | FormDescription.displayName = "FormDescription"
142 |
143 | const FormMessage = React.forwardRef<
144 | HTMLParagraphElement,
145 | React.HTMLAttributes
146 | >(({ className, children, ...props }, ref) => {
147 | const { error, formMessageId } = useFormField()
148 | const body = error ? String(error?.message) : children
149 |
150 | if (!body) {
151 | return null
152 | }
153 |
154 | return (
155 |
161 | {body}
162 |
163 | )
164 | })
165 | FormMessage.displayName = "FormMessage"
166 |
167 | export {
168 | useFormField,
169 | Form,
170 | FormItem,
171 | FormLabel,
172 | FormControl,
173 | FormDescription,
174 | FormMessage,
175 | FormField,
176 | }
177 |
--------------------------------------------------------------------------------
/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 |
3 | import { cn } from "@/lib/utils"
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | )
21 | }
22 | )
23 | Input.displayName = "Input"
24 |
25 | export { Input }
26 |
--------------------------------------------------------------------------------
/components/ui/label.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as LabelPrimitive from "@radix-ui/react-label"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const labelVariants = cva(
10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
11 | )
12 |
13 | const Label = React.forwardRef<
14 | React.ElementRef,
15 | React.ComponentPropsWithoutRef &
16 | VariantProps
17 | >(({ className, ...props }, ref) => (
18 |
23 | ))
24 | Label.displayName = LabelPrimitive.Root.displayName
25 |
26 | export { Label }
27 |
--------------------------------------------------------------------------------
/components/ui/progress.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as ProgressPrimitive from "@radix-ui/react-progress"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Progress = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, value, ...props }, ref) => (
12 |
20 |
24 |
25 | ))
26 | Progress.displayName = ProgressPrimitive.Root.displayName
27 |
28 | export { Progress }
29 |
--------------------------------------------------------------------------------
/components/ui/select.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SelectPrimitive from "@radix-ui/react-select"
5 | import { Check, ChevronDown } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const Select = SelectPrimitive.Root
10 |
11 | const SelectGroup = SelectPrimitive.Group
12 |
13 | const SelectValue = SelectPrimitive.Value
14 |
15 | const SelectTrigger = React.forwardRef<
16 | React.ElementRef,
17 | React.ComponentPropsWithoutRef
18 | >(({ className, children, ...props }, ref) => (
19 |
27 | {children}
28 |
29 |
30 |
31 |
32 | ))
33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
34 |
35 | const SelectContent = React.forwardRef<
36 | React.ElementRef,
37 | React.ComponentPropsWithoutRef
38 | >(({ className, children, position = "popper", ...props }, ref) => (
39 |
40 |
51 |
58 | {children}
59 |
60 |
61 |
62 | ))
63 | SelectContent.displayName = SelectPrimitive.Content.displayName
64 |
65 | const SelectLabel = React.forwardRef<
66 | React.ElementRef,
67 | React.ComponentPropsWithoutRef
68 | >(({ className, ...props }, ref) => (
69 |
74 | ))
75 | SelectLabel.displayName = SelectPrimitive.Label.displayName
76 |
77 | const SelectItem = React.forwardRef<
78 | React.ElementRef,
79 | React.ComponentPropsWithoutRef
80 | >(({ className, children, ...props }, ref) => (
81 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | {children}
96 |
97 | ))
98 | SelectItem.displayName = SelectPrimitive.Item.displayName
99 |
100 | const SelectSeparator = React.forwardRef<
101 | React.ElementRef,
102 | React.ComponentPropsWithoutRef
103 | >(({ className, ...props }, ref) => (
104 |
109 | ))
110 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName
111 |
112 | export {
113 | Select,
114 | SelectGroup,
115 | SelectValue,
116 | SelectTrigger,
117 | SelectContent,
118 | SelectLabel,
119 | SelectItem,
120 | SelectSeparator,
121 | }
122 |
--------------------------------------------------------------------------------
/components/ui/sheet.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SheetPrimitive from "@radix-ui/react-dialog"
5 | import { cva, type VariantProps } from "class-variance-authority"
6 | import { X } from "lucide-react"
7 |
8 | import { cn } from "@/lib/utils"
9 |
10 | const Sheet = SheetPrimitive.Root
11 |
12 | const SheetTrigger = SheetPrimitive.Trigger
13 |
14 | const SheetClose = SheetPrimitive.Close
15 |
16 | const SheetPortal = ({
17 | className,
18 | ...props
19 | }: SheetPrimitive.DialogPortalProps) => (
20 |
21 | )
22 | SheetPortal.displayName = SheetPrimitive.Portal.displayName
23 |
24 | const SheetOverlay = React.forwardRef<
25 | React.ElementRef,
26 | React.ComponentPropsWithoutRef
27 | >(({ className, ...props }, ref) => (
28 |
36 | ))
37 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
38 |
39 | const sheetVariants = cva(
40 | "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
41 | {
42 | variants: {
43 | side: {
44 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
45 | bottom:
46 | "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
47 | left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
48 | right:
49 | "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
50 | },
51 | },
52 | defaultVariants: {
53 | side: "right",
54 | },
55 | }
56 | )
57 |
58 | interface SheetContentProps
59 | extends React.ComponentPropsWithoutRef,
60 | VariantProps {}
61 |
62 | const SheetContent = React.forwardRef<
63 | React.ElementRef,
64 | SheetContentProps
65 | >(({ side = "right", className, children, ...props }, ref) => (
66 |
67 |
68 |
73 | {children}
74 |
75 |
76 | Close
77 |
78 |
79 |
80 | ))
81 | SheetContent.displayName = SheetPrimitive.Content.displayName
82 |
83 | const SheetHeader = ({
84 | className,
85 | ...props
86 | }: React.HTMLAttributes) => (
87 |
94 | )
95 | SheetHeader.displayName = "SheetHeader"
96 |
97 | const SheetFooter = ({
98 | className,
99 | ...props
100 | }: React.HTMLAttributes) => (
101 |
108 | )
109 | SheetFooter.displayName = "SheetFooter"
110 |
111 | const SheetTitle = React.forwardRef<
112 | React.ElementRef,
113 | React.ComponentPropsWithoutRef
114 | >(({ className, ...props }, ref) => (
115 |
120 | ))
121 | SheetTitle.displayName = SheetPrimitive.Title.displayName
122 |
123 | const SheetDescription = React.forwardRef<
124 | React.ElementRef,
125 | React.ComponentPropsWithoutRef
126 | >(({ className, ...props }, ref) => (
127 |
132 | ))
133 | SheetDescription.displayName = SheetPrimitive.Description.displayName
134 |
135 | export {
136 | Sheet,
137 | SheetTrigger,
138 | SheetClose,
139 | SheetContent,
140 | SheetHeader,
141 | SheetFooter,
142 | SheetTitle,
143 | SheetDescription,
144 | }
145 |
--------------------------------------------------------------------------------
/components/user-avatar.tsx:
--------------------------------------------------------------------------------
1 | import { useUser } from "@clerk/nextjs";
2 | import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
3 |
4 | export const UserAvatar = () => {
5 | const { user } = useUser();
6 | return (
7 |
8 |
9 |
10 | {user?.firstName?.charAt(0)}
11 | {user?.lastName?.charAt(0)}
12 |
13 |
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/constants.ts:
--------------------------------------------------------------------------------
1 | export const MAX_FREE_COUNTS = 5;
2 |
--------------------------------------------------------------------------------
/hooks/use-pro-modal.tsx:
--------------------------------------------------------------------------------
1 | import { create } from "zustand";
2 |
3 | interface useProModalStore {
4 | isOpen: boolean;
5 | onOpen: () => void;
6 | onClose: () => void;
7 | }
8 |
9 | export const useProModal = create((set) => ({
10 | isOpen: false,
11 | onOpen: () => set({ isOpen: true }),
12 | onClose: () => set({ isOpen: false }),
13 | }));
14 |
15 | export default useProModal;
16 |
--------------------------------------------------------------------------------
/lib/api-limit.ts:
--------------------------------------------------------------------------------
1 | import { auth } from "@clerk/nextjs";
2 |
3 | import { MAX_FREE_COUNTS } from "@/constants";
4 | import prismadb from "./prismadb";
5 |
6 | export const increaseApiLimit = async () => {
7 | const { userId } = auth();
8 |
9 | if (!userId) {
10 | return;
11 | }
12 |
13 | const userApiLimit = await prismadb.userApiLimit.findUnique({
14 | where: {
15 | userId,
16 | },
17 | });
18 |
19 | if (!userApiLimit) {
20 | await prismadb.userApiLimit.create({
21 | data: {
22 | userId,
23 | count: 1,
24 | },
25 | });
26 | } else {
27 | await prismadb.userApiLimit.update({
28 | where: {
29 | userId,
30 | },
31 | data: {
32 | count: userApiLimit.count + 1,
33 | },
34 | });
35 | }
36 | };
37 |
38 | export const checkApiLimit = async () => {
39 | const { userId } = auth();
40 |
41 | if (!userId) {
42 | return false;
43 | }
44 |
45 | const userApiLimit = await prismadb.userApiLimit.findUnique({
46 | where: {
47 | userId,
48 | },
49 | });
50 |
51 | if (!userApiLimit || userApiLimit.count < MAX_FREE_COUNTS) {
52 | return true;
53 | } else {
54 | return false;
55 | }
56 | };
57 |
58 | export const getApiLimitCount = async () => {
59 | const { userId } = auth();
60 |
61 | if (!userId) {
62 | return 0;
63 | }
64 |
65 | const userApiLimit = await prismadb.userApiLimit.findUnique({
66 | where: {
67 | userId,
68 | },
69 | });
70 |
71 | if (!userApiLimit) {
72 | return 0;
73 | } else {
74 | return userApiLimit.count;
75 | }
76 | };
77 |
--------------------------------------------------------------------------------
/lib/prismadb.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from "@prisma/client";
2 |
3 | declare global {
4 | var prisma: PrismaClient | undefined;
5 | }
6 |
7 | const prismadb = globalThis.prisma || new PrismaClient();
8 | if (process.env.NODE_ENV !== "production") globalThis.prisma = prismadb;
9 |
10 | export default prismadb;
11 |
--------------------------------------------------------------------------------
/lib/stripe.ts:
--------------------------------------------------------------------------------
1 | import Stripe from "stripe";
2 |
3 | export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
4 | apiVersion: "2022-11-15",
5 | typescript: true,
6 | });
7 |
--------------------------------------------------------------------------------
/lib/subscription.ts:
--------------------------------------------------------------------------------
1 | import { auth } from "@clerk/nextjs";
2 |
3 | import prismadb from "./prismadb";
4 |
5 | const DAY_IN_MS = 1000 * 60 * 60 * 24;
6 |
7 | export const checkSubscription = async () => {
8 | const { userId } = auth();
9 |
10 | if (!userId) {
11 | return false;
12 | }
13 |
14 | const userSubscription = await prismadb.userSubscription.findUnique({
15 | where: {
16 | userId,
17 | },
18 | select: {
19 | stripeSubscriptionId: true,
20 | stripeCurrentPeriodEnd: true,
21 | stripeCustomerId: true,
22 | stripePriceId: true,
23 | },
24 | });
25 |
26 | if (!userSubscription) {
27 | return false;
28 | }
29 |
30 | const isValid = userSubscription.stripePriceId && userSubscription.stripeCurrentPeriodEnd?.getTime()! + DAY_IN_MS > Date.now();
31 |
32 | return !!isValid;
33 | };
34 |
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx";
2 | import { twMerge } from "tailwind-merge";
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs));
6 | }
7 |
8 | export function absoluteUrl(path: string) {
9 | return `${process.env.NEXT_PUBLIC_APP_URL}${path}`;
10 | }
11 |
--------------------------------------------------------------------------------
/middleware.ts:
--------------------------------------------------------------------------------
1 | import { authMiddleware } from "@clerk/nextjs";
2 |
3 | // This example protects all routes including api/trpc routes
4 | // Please edit this to allow other routes to be public as needed.
5 | // See https://clerk.com/docs/nextjs/middleware for more information about configuring your middleware
6 | export default authMiddleware({
7 | publicRoutes: ["/", "/api/webhook"],
8 | });
9 |
10 | export const config = {
11 | matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"],
12 | };
13 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | images: {
4 | domains: ["oaidalleapiprodscus.blob.core.windows.net"],
5 | },
6 | };
7 |
8 | module.exports = nextConfig;
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ai-saas",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "@clerk/nextjs": "^4.23.0",
13 | "@hookform/resolvers": "^3.1.1",
14 | "@prisma/client": "^5.0.0",
15 | "@radix-ui/react-avatar": "^1.0.3",
16 | "@radix-ui/react-dialog": "^1.0.4",
17 | "@radix-ui/react-label": "^2.0.2",
18 | "@radix-ui/react-progress": "^1.0.3",
19 | "@radix-ui/react-select": "^1.2.2",
20 | "@radix-ui/react-slot": "^1.0.2",
21 | "@types/node": "20.4.3",
22 | "@types/react": "18.2.15",
23 | "@types/react-dom": "18.2.7",
24 | "autoprefixer": "10.4.14",
25 | "axios": "^1.4.0",
26 | "class-variance-authority": "^0.7.0",
27 | "clsx": "^2.0.0",
28 | "crisp-sdk-web": "^1.0.19",
29 | "eslint": "8.45.0",
30 | "eslint-config-next": "13.4.12",
31 | "lucide-react": "^0.263.0",
32 | "next": "13.4.12",
33 | "openai": "^3.3.0",
34 | "postcss": "8.4.27",
35 | "react": "18.2.0",
36 | "react-dom": "18.2.0",
37 | "react-hook-form": "^7.45.2",
38 | "react-hot-toast": "^2.4.1",
39 | "react-markdown": "^8.0.7",
40 | "replicate": "^0.12.3",
41 | "stripe": "^12.16.0",
42 | "tailwind-merge": "^1.14.0",
43 | "tailwindcss": "3.3.3",
44 | "tailwindcss-animate": "^1.0.6",
45 | "typescript": "5.1.6",
46 | "typewriter-effect": "^2.20.1",
47 | "zod": "^3.21.4",
48 | "zustand": "^4.3.9"
49 | },
50 | "devDependencies": {
51 | "prisma": "^5.0.0"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | generator client {
2 | provider = "prisma-client-js"
3 | }
4 |
5 | datasource db {
6 | provider = "mysql"
7 | url = env("DATABASE_URL")
8 | relationMode = "prisma"
9 | }
10 |
11 | model UserApiLimit {
12 | id String @id @default(cuid())
13 | userId String @unique
14 | count Int @default(0)
15 | createdAt DateTime @default(now())
16 | updatedAt DateTime @updatedAt
17 | }
18 |
19 | model UserSubscription {
20 | id String @id @default(cuid())
21 | userId String @unique
22 | stripeCustomerId String? @unique @map(name: "stripe_customer_id")
23 | stripeSubscriptionId String? @unique @map(name: "stripe_subscription_id")
24 | stripePriceId String? @unique @map(name: "stripe_price_id")
25 | stripeCurrentPeriodEnd DateTime? @map(name: "stripe_current_period_end")
26 | createdAt DateTime @default(now())
27 | updatedAt DateTime @updatedAt
28 | }
--------------------------------------------------------------------------------
/public/empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayusshrathore/ai-saas/623dba771e012fb2923c0ef6ded4127bed09181e/public/empty.png
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ayusshrathore/ai-saas/623dba771e012fb2923c0ef6ded4127bed09181e/public/logo.png
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | darkMode: ["class"],
4 | content: [
5 | './pages/**/*.{ts,tsx}',
6 | './components/**/*.{ts,tsx}',
7 | './app/**/*.{ts,tsx}',
8 | './src/**/*.{ts,tsx}',
9 | ],
10 | theme: {
11 | container: {
12 | center: true,
13 | padding: "2rem",
14 | screens: {
15 | "2xl": "1400px",
16 | },
17 | },
18 | extend: {
19 | colors: {
20 | border: "hsl(var(--border))",
21 | input: "hsl(var(--input))",
22 | ring: "hsl(var(--ring))",
23 | background: "hsl(var(--background))",
24 | foreground: "hsl(var(--foreground))",
25 | primary: {
26 | DEFAULT: "hsl(var(--primary))",
27 | foreground: "hsl(var(--primary-foreground))",
28 | },
29 | secondary: {
30 | DEFAULT: "hsl(var(--secondary))",
31 | foreground: "hsl(var(--secondary-foreground))",
32 | },
33 | destructive: {
34 | DEFAULT: "hsl(var(--destructive))",
35 | foreground: "hsl(var(--destructive-foreground))",
36 | },
37 | muted: {
38 | DEFAULT: "hsl(var(--muted))",
39 | foreground: "hsl(var(--muted-foreground))",
40 | },
41 | accent: {
42 | DEFAULT: "hsl(var(--accent))",
43 | foreground: "hsl(var(--accent-foreground))",
44 | },
45 | popover: {
46 | DEFAULT: "hsl(var(--popover))",
47 | foreground: "hsl(var(--popover-foreground))",
48 | },
49 | card: {
50 | DEFAULT: "hsl(var(--card))",
51 | foreground: "hsl(var(--card-foreground))",
52 | },
53 | },
54 | borderRadius: {
55 | lg: "var(--radius)",
56 | md: "calc(var(--radius) - 2px)",
57 | sm: "calc(var(--radius) - 4px)",
58 | },
59 | keyframes: {
60 | "accordion-down": {
61 | from: { height: 0 },
62 | to: { height: "var(--radix-accordion-content-height)" },
63 | },
64 | "accordion-up": {
65 | from: { height: "var(--radix-accordion-content-height)" },
66 | to: { height: 0 },
67 | },
68 | },
69 | animation: {
70 | "accordion-down": "accordion-down 0.2s ease-out",
71 | "accordion-up": "accordion-up 0.2s ease-out",
72 | },
73 | },
74 | },
75 | plugins: [require("tailwindcss-animate")],
76 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "bundler",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true,
17 | "plugins": [
18 | {
19 | "name": "next"
20 | }
21 | ],
22 | "paths": {
23 | "@/*": ["./*"]
24 | }
25 | },
26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
27 | "exclude": ["node_modules"]
28 | }
29 |
--------------------------------------------------------------------------------