├── .eslintrc.json
├── app
├── favicon.ico
├── (auth)
│ ├── (routes)
│ │ ├── sign-in
│ │ │ └── [[...sign-in]]
│ │ │ │ └── page.tsx
│ │ └── sign-up
│ │ │ └── [[...sign-up]]
│ │ │ └── page.tsx
│ └── layout.tsx
├── (dashboard)
│ ├── (routes)
│ │ ├── video
│ │ │ ├── constant.ts
│ │ │ └── page.tsx
│ │ ├── code
│ │ │ ├── constant.ts
│ │ │ └── page.tsx
│ │ ├── music
│ │ │ ├── constant.ts
│ │ │ └── page.tsx
│ │ ├── settings
│ │ │ ├── constant.ts
│ │ │ └── page.tsx
│ │ ├── conversation
│ │ │ ├── constant.ts
│ │ │ └── page.tsx
│ │ ├── image
│ │ │ ├── constant.ts
│ │ │ └── page.tsx
│ │ └── dashboard
│ │ │ └── page.tsx
│ └── layout.tsx
├── (landing)
│ ├── page.tsx
│ └── layout.tsx
├── layout.tsx
├── api
│ ├── music
│ │ └── route.ts
│ ├── video
│ │ └── route.ts
│ ├── conversation
│ │ └── route.ts
│ ├── image
│ │ └── route.ts
│ ├── code
│ │ └── route.ts
│ ├── stripe
│ │ └── route.ts
│ └── webhook
│ │ └── route.ts
└── globals.css
├── public
├── empty.png
├── logo.png
├── vercel.svg
└── next.svg
├── postcss.config.js
├── components
├── toaster-provider.tsx
├── crisp-provider.tsx
├── bot-avatar.tsx
├── crisp-chat.tsx
├── theme-provider.tsx
├── modal-provider.tsx
├── user-avatar.tsx
├── loader.tsx
├── empty.tsx
├── navbar.tsx
├── ui
│ ├── label.tsx
│ ├── separator.tsx
│ ├── progress.tsx
│ ├── input.tsx
│ ├── badge.tsx
│ ├── avatar.tsx
│ ├── button.tsx
│ ├── card.tsx
│ ├── dialog.tsx
│ ├── select.tsx
│ ├── form.tsx
│ ├── sheet.tsx
│ ├── command.tsx
│ └── dropdown-menu.tsx
├── heading.tsx
├── subscription-button.tsx
├── mobile-sidebar.tsx
├── landing-navbar.tsx
├── free-counter.tsx
├── moddle-toggle.tsx
├── landing-hero.tsx
├── pro-modal.tsx
└── sidebar.tsx
├── lib
├── stripe.ts
├── utils.ts
├── prismadb.ts
├── subscription.ts
└── api-limit.ts
├── next.config.js
├── hooks
└── use-pro-modal.tsx
├── components.json
├── middleware.ts
├── .gitignore
├── tailwind.config.ts
├── tsconfig.json
├── prisma
└── schema.prisma
├── constant.ts
├── README.md
├── package.json
└── tailwind.config.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "next/core-web-vitals"
3 | }
4 |
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruxin23/ai-saas/HEAD/app/favicon.ico
--------------------------------------------------------------------------------
/public/empty.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruxin23/ai-saas/HEAD/public/empty.png
--------------------------------------------------------------------------------
/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ruxin23/ai-saas/HEAD/public/logo.png
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/app/(auth)/(routes)/sign-in/[[...sign-in]]/page.tsx:
--------------------------------------------------------------------------------
1 | import { SignIn } from "@clerk/nextjs";
2 |
3 | export default function Page() {
4 | return ;
5 | }
--------------------------------------------------------------------------------
/app/(auth)/(routes)/sign-up/[[...sign-up]]/page.tsx:
--------------------------------------------------------------------------------
1 | import { SignUp } from "@clerk/nextjs";
2 |
3 | export default function Page() {
4 | return ;
5 | }
--------------------------------------------------------------------------------
/components/toaster-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Toaster } from "react-hot-toast"
4 |
5 | export const ToasterProvider = () => {
6 | return
7 | };
--------------------------------------------------------------------------------
/lib/stripe.ts:
--------------------------------------------------------------------------------
1 | import Stripe from 'stripe'
2 |
3 | export const stripe = new Stripe(process.env.STRIPE_API_KEY!, {
4 | apiVersion: '2023-08-16',
5 | typescript: true,
6 | })
--------------------------------------------------------------------------------
/components/crisp-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { CrispChat } from "@/components/crisp-chat";
4 |
5 | export const CrispProvider = () => {
6 | return
7 | };
8 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/video/constant.ts:
--------------------------------------------------------------------------------
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 | });
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/code/constant.ts:
--------------------------------------------------------------------------------
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/constant.ts:
--------------------------------------------------------------------------------
1 | import * as z from "zod";
2 |
3 | export const formSchema = z.object({
4 | prompt: z.string().min(1, {
5 | message: "Music prompt is required"
6 | }),
7 | });
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/settings/constant.ts:
--------------------------------------------------------------------------------
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/constant.ts:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {
3 | images: {
4 | domains: [
5 | "oaidalleapiprodscus.blob.core.windows.net"
6 | ]
7 | }
8 | }
9 |
10 | module.exports = nextConfig
11 |
--------------------------------------------------------------------------------
/components/bot-avatar.tsx:
--------------------------------------------------------------------------------
1 | import { Avatar, AvatarImage } from "@/components/ui/avatar";
2 |
3 | export const BotAvatar = () => {
4 | return (
5 |
6 |
7 |
8 | );
9 | };
--------------------------------------------------------------------------------
/components/crisp-chat.tsx:
--------------------------------------------------------------------------------
1 |
2 | import { useEffect } from "react";
3 | import { Crisp } from "crisp-sdk-web";
4 |
5 | export const CrispChat = () => {
6 | useEffect(() => {
7 | Crisp.configure("22cd3a04-d375-4031-8827-3b40993cd44a");
8 | }, []);
9 |
10 | return null;
11 | };
--------------------------------------------------------------------------------
/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } 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 | }
--------------------------------------------------------------------------------
/lib/prismadb.ts:
--------------------------------------------------------------------------------
1 | import { PrismaClient } from "@prisma/client";
2 | declare global {
3 | var prisma: PrismaClient | undefined;
4 | }
5 |
6 | export const db = globalThis.prisma || new PrismaClient();
7 |
8 | if (process.env.NODE_ENV !== "production") globalThis.prisma = db;
9 |
10 | export default db
11 |
--------------------------------------------------------------------------------
/app/(auth)/layout.tsx:
--------------------------------------------------------------------------------
1 | const AuthLayout = ({
2 | children
3 | }: {
4 | children: React.ReactNode;
5 | }) => {
6 | return (
7 |
8 | {children}
9 |
10 | );
11 | }
12 |
13 | export default AuthLayout;
--------------------------------------------------------------------------------
/app/(landing)/page.tsx:
--------------------------------------------------------------------------------
1 | import { LandingNavbar } from "@/components/landing-navbar";
2 | import { LandingHero } from "@/components/landing-hero";
3 |
4 | const LandingPage = () => {
5 | return (
6 |
7 |
8 |
9 |
10 | );
11 | }
12 |
13 | export default LandingPage;
--------------------------------------------------------------------------------
/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 | }))
--------------------------------------------------------------------------------
/components/theme-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { ThemeProvider as NextThemesProvider } from "next-themes"
5 | import { type ThemeProviderProps } from "next-themes/dist/types"
6 |
7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
8 | return {children}
9 | }
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/app/(landing)/layout.tsx:
--------------------------------------------------------------------------------
1 | const LandingLayout = ({
2 | children
3 | }: {
4 | children: React.ReactNode;
5 | }) => {
6 | return (
7 |
8 |
9 | {children}
10 |
11 |
12 | );
13 | }
14 |
15 | export default LandingLayout;
--------------------------------------------------------------------------------
/components/modal-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useEffect, useState } from "react";
4 |
5 | import {ProModal} from "@/components/pro-modal";
6 |
7 | export const ModalProvider = () => {
8 | const [isMounted, setIsMounted] = useState(false);
9 |
10 | useEffect(() => {
11 | setIsMounted(true);
12 | }, []);
13 |
14 | if (!isMounted) {
15 | return null;
16 | }
17 |
18 | return (
19 | <>
20 |
21 | >
22 | );
23 | };
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 | # vercel
31 | .vercel
32 |
33 | # typescript
34 | *.tsbuildinfo
35 | next-env.d.ts
36 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss'
2 |
3 | const config: Config = {
4 | content: [
5 | './pages/**/*.{js,ts,jsx,tsx,mdx}',
6 | './components/**/*.{js,ts,jsx,tsx,mdx}',
7 | './app/**/*.{js,ts,jsx,tsx,mdx}',
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
13 | 'gradient-conic':
14 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
15 | },
16 | },
17 | },
18 | plugins: [],
19 | }
20 | export default config
21 |
--------------------------------------------------------------------------------
/components/user-avatar.tsx:
--------------------------------------------------------------------------------
1 | import { useUser } from "@clerk/nextjs";
2 | import {
3 | Avatar,
4 | AvatarFallback,
5 | AvatarImage
6 | } from "@/components/ui/avatar";
7 | const UserAvatar = () => {
8 | const { user } = useUser();
9 | return (
10 |
11 |
12 |
13 | {user?.firstName?.charAt(0)}
14 | {user?.lastName?.charAt(0)}
15 |
16 |
17 | );
18 | }
19 |
20 | export default UserAvatar;
--------------------------------------------------------------------------------
/components/loader.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image"
2 |
3 | export const Loader = () => {
4 | return (
5 |
6 |
7 |
12 |
13 |
14 | Genius is thinking...
15 |
16 |
17 | );
18 | };
--------------------------------------------------------------------------------
/components/empty.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 |
3 | interface EmptyProps {
4 | label: string;
5 | }
6 | const Empty = ({
7 | label
8 | }: EmptyProps) => {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 | {label}
16 |
17 |
18 | );
19 | }
20 |
21 | export default Empty;
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
/components/navbar.tsx:
--------------------------------------------------------------------------------
1 | import { UserButton } from "@clerk/nextjs"
2 | import MobileSidebar from './mobile-sidebar'
3 | import { getApiLimitCount } from "@/lib/api-limit"
4 | import { checkSubscription } from "@/lib/subscription";
5 | import { ModeToggle } from "./moddle-toggle";
6 |
7 | const Navbar = async () => {
8 | const apiLimitCount = await getApiLimitCount()
9 | const isPro = await checkSubscription()
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | )
19 | }
20 |
21 | export default Navbar
22 |
23 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/lib/subscription.ts:
--------------------------------------------------------------------------------
1 | import { auth } from "@clerk/nextjs";
2 |
3 | import prismadb from "@/lib/prismadb";
4 |
5 | const DAY_IN_MS = 86_400_000;
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: 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 =
31 | userSubscription.stripePriceId &&
32 | userSubscription.stripeCurrentPeriodEnd?.getTime()! + DAY_IN_MS > Date.now()
33 |
34 | return !!isValid;
35 | };
36 |
--------------------------------------------------------------------------------
/components/ui/separator.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as SeparatorPrimitive from "@radix-ui/react-separator"
5 |
6 | import { cn } from "@/lib/utils"
7 |
8 | const Separator = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(
12 | (
13 | { className, orientation = "horizontal", decorative = true, ...props },
14 | ref
15 | ) => (
16 |
27 | )
28 | )
29 | Separator.displayName = SeparatorPrimitive.Root.displayName
30 |
31 | export { Separator }
32 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 ({
7 | children
8 | }: {
9 | children: React.ReactNode
10 | }) => {
11 | const isPro = await checkSubscription()
12 | const apiLimitCount = await getApiLimitCount()
13 | return (
14 |
15 |
17 |
18 |
19 |
20 |
21 | {children}
22 |
23 |
24 | )
25 | }
26 |
27 | export default DashboardLayout
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/image/constant.ts:
--------------------------------------------------------------------------------
1 | import * as z from "zod";
2 |
3 | export const formSchema = z.object({
4 | prompt: z.string().min(1, {
5 | message: "Photo 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 |
--------------------------------------------------------------------------------
/prisma/schema.prisma:
--------------------------------------------------------------------------------
1 | // This is your Prisma schema file,
2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema
3 |
4 | datasource db {
5 | provider = "mysql"
6 | url = env("DATABASE_URL")
7 | relationMode = "prisma"
8 | }
9 |
10 | generator client {
11 | provider = "prisma-client-js"
12 | }
13 |
14 | model UserApiLimit {
15 | id String @id @default(cuid())
16 | userId String @unique
17 | count Int @default(0)
18 | createdAt DateTime @default(now())
19 | updatedAt DateTime @updatedAt
20 | }
21 |
22 | model UserSubscription {
23 | id String @id @default(cuid())
24 | userId String @unique
25 | stripeCustomerId String? @unique @map(name: "stripe_customer_id")
26 | stripeSubscriptionId String? @unique @map(name: "stripe_subscription_id")
27 | stripePriceId String? @map(name: "stripe_price_id")
28 | stripeCurrentPeriodEnd DateTime? @map(name: "stripe_current_period_end")
29 | }
30 |
--------------------------------------------------------------------------------
/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 |
27 |
{title}
28 |
29 | {description}
30 |
31 |
32 |
33 | >
34 | );
35 | };
--------------------------------------------------------------------------------
/constant.ts:
--------------------------------------------------------------------------------
1 | import { Code, ImageIcon, MessageSquare, Music, VideoIcon } from "lucide-react";
2 |
3 | export const MAX_FREE_COUNTS = 5;
4 |
5 | export const tools = [
6 | {
7 | label: "Conversation",
8 | icon: MessageSquare,
9 | href: "/conversation",
10 | color: "text-violet-500",
11 | bgColor: "bg-violet-500/10",
12 | },
13 | {
14 | label: "Music Generation",
15 | icon: Music,
16 | href: "/music",
17 | color: "text-emerald-500",
18 | bgColor: "bg-emerald-500/10",
19 | },
20 | {
21 | label: "Image Generation",
22 | icon: ImageIcon,
23 | color: "text-pink-700",
24 | bgColor: "bg-pink-700/10",
25 | href: "/image",
26 | },
27 | {
28 | label: "Video Generation",
29 | icon: VideoIcon,
30 | color: "text-orange-700",
31 | bgColor: "bg-orange-700/10",
32 | href: "/video",
33 | },
34 | {
35 | label: "Code Generation",
36 | icon: Code,
37 | color: "text-green-700",
38 | bgColor: "bg-green-700/10",
39 | href: "/code",
40 | },
41 | ];
42 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/settings/page.tsx:
--------------------------------------------------------------------------------
1 | import { Settings } from "lucide-react";
2 |
3 | import { Heading } from "@/components/heading";
4 | import { SubscriptionButton } from "@/components/subscription-button";
5 | import { checkSubscription } from "@/lib/subscription";
6 |
7 | const SettingsPage = async () => {
8 | const isPro = await checkSubscription();
9 |
10 | return (
11 |
12 |
19 |
20 |
21 | {isPro ? "You are currently on a Pro plan." : "You are currently on a free plan."}
22 |
23 |
24 |
25 |
26 | );
27 | }
28 |
29 | export default SettingsPage;
30 |
31 |
--------------------------------------------------------------------------------
/components/subscription-button.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import axios from "axios";
4 | import { useState } from "react";
5 | import { Zap } from "lucide-react";
6 | import { toast } from "react-hot-toast";
7 |
8 | import { Button } from "@/components/ui/button";
9 |
10 | export const SubscriptionButton = ({
11 | isPro = false
12 | }: {
13 | isPro: boolean;
14 | }) => {
15 | const [loading, setLoading] = useState(false);
16 |
17 | const onClick = async () => {
18 | try {
19 | setLoading(true);
20 |
21 | const response = await axios.get("/api/stripe");
22 |
23 | window.location.href = response.data.url;
24 | } catch (error) {
25 | toast.error("Something went wrong");
26 | } finally {
27 | setLoading(false);
28 | }
29 | };
30 |
31 | return (
32 |
36 | )
37 | };
38 |
--------------------------------------------------------------------------------
/components/mobile-sidebar.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { Menu } from "lucide-react";
3 | import { Button } from "@/components/ui/button";
4 | import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"
5 | import Sidebar from "./sidebar";
6 | import { useEffect, useState } from "react";
7 |
8 | interface MobileSidebarProps {
9 | apiLimitCount: number
10 | isPro: boolean
11 | }
12 | const MobileSidebar = ({ apiLimitCount, isPro = false }: MobileSidebarProps) => {
13 | const [isMounted, setIsMounted] = useState(false)
14 | useEffect(() => {
15 | setIsMounted(true)
16 | }, [])
17 | if (!isMounted) return null
18 | return (
19 |
20 |
21 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | }
32 |
33 | export default MobileSidebar;
--------------------------------------------------------------------------------
/components/landing-navbar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { Montserrat } from "next/font/google";
4 | import Image from "next/image"
5 | import Link from "next/link"
6 | import { useAuth } from "@clerk/nextjs";
7 |
8 | import { cn } from "@/lib/utils";
9 | import { Button } from "@/components/ui/button";
10 |
11 | const font = Montserrat({ weight: '600', subsets: ['latin'] });
12 |
13 | export const LandingNavbar = () => {
14 | const { isSignedIn } = useAuth();
15 |
16 | return (
17 |
34 | )
35 | }
--------------------------------------------------------------------------------
/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import './globals.css'
2 | import type { Metadata } from 'next'
3 | import { Inter } from 'next/font/google'
4 | import { ClerkProvider } from '@clerk/nextjs'
5 | import { ToasterProvider } from '@/components/toaster-provider'
6 | import { ModalProvider } from '@/components/modal-provider'
7 | import { CrispProvider } from '@/components/crisp-provider'
8 | import { ThemeProvider } from '@/components/theme-provider'
9 | const inter = Inter({ subsets: ['latin'] })
10 |
11 | export const metadata: Metadata = {
12 | title: 'Genius',
13 | description: 'Genius is a platform for building AI-powered apps.',
14 | }
15 |
16 | export default function RootLayout({
17 | children,
18 | }: {
19 | children: React.ReactNode
20 | }) {
21 | return (
22 |
23 |
24 |
25 |
26 |
31 |
32 |
33 |
34 | {children}
35 |
36 |
37 |
38 |
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/components/ui/badge.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { cva, type VariantProps } from "class-variance-authority"
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:
12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13 | secondary:
14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15 | destructive:
16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17 | outline: "text-foreground",
18 | premium: "bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 text-primary-foreground border-0"
19 | },
20 | },
21 | defaultVariants: {
22 | variant: "default",
23 | },
24 | }
25 | )
26 |
27 | export interface BadgeProps
28 | extends React.HTMLAttributes,
29 | VariantProps {}
30 |
31 | function Badge({ className, variant, ...props }: BadgeProps) {
32 | return (
33 |
34 | )
35 | }
36 |
37 | export { Badge, badgeVariants }
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
37 |
--------------------------------------------------------------------------------
/lib/api-limit.ts:
--------------------------------------------------------------------------------
1 | import { auth } from "@clerk/nextjs";
2 |
3 | import prismadb from "@/lib/prismadb";
4 | import { MAX_FREE_COUNTS } from "@/constant";
5 |
6 | export const incrementApiLimit = async () => {
7 | const { userId } = auth();
8 |
9 | if (!userId) {
10 | return;
11 | }
12 |
13 | const userApiLimit = await prismadb.userApiLimit.findUnique({
14 | where: { userId: userId },
15 | });
16 |
17 | if (userApiLimit) {
18 | await prismadb.userApiLimit.update({
19 | where: { userId: userId },
20 | data: { count: userApiLimit.count + 1 },
21 | });
22 | } else {
23 | await prismadb.userApiLimit.create({
24 | data: { userId: userId, count: 1 },
25 | });
26 | }
27 | };
28 |
29 | export const checkApiLimit = async () => {
30 | const { userId } = auth();
31 |
32 | if (!userId) {
33 | return false;
34 | }
35 |
36 | const userApiLimit = await prismadb.userApiLimit.findUnique({
37 | where: { userId: userId },
38 | });
39 |
40 | if (!userApiLimit || userApiLimit.count < MAX_FREE_COUNTS) {
41 | return true;
42 | } else {
43 | return false;
44 | }
45 | };
46 |
47 | export const getApiLimitCount = async () => {
48 | const { userId } = auth();
49 |
50 | if (!userId) {
51 | return 0;
52 | }
53 |
54 | const userApiLimit = await prismadb.userApiLimit.findUnique({
55 | where: {
56 | userId
57 | }
58 | });
59 |
60 | if (!userApiLimit) {
61 | return 0;
62 | }
63 |
64 | return userApiLimit.count;
65 | };
--------------------------------------------------------------------------------
/components/free-counter.tsx:
--------------------------------------------------------------------------------
1 | import { Zap } from "lucide-react";
2 | import { useEffect, useState } from "react";
3 |
4 | import { MAX_FREE_COUNTS } from "@/constant";
5 | import { Card, CardContent } from "@/components/ui/card";
6 | import { Button } from "@/components/ui/button";
7 | import { Progress } from "@/components/ui/progress";
8 | import { useProModal } from "@/hooks/use-pro-modal";
9 |
10 | export const FreeCounter = ({
11 | isPro = false,
12 | apiLimitCount = 0,
13 | }: {
14 | isPro: boolean,
15 | apiLimitCount: number
16 | }) => {
17 | const [mounted, setMounted] = useState(false);
18 | const proModal = useProModal();
19 |
20 | useEffect(() => {
21 | setMounted(true);
22 | }, []);
23 |
24 | if (!mounted) {
25 | return null;
26 | }
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 |
47 |
48 |
49 |
50 | )
51 | }
--------------------------------------------------------------------------------
/app/api/music/route.ts:
--------------------------------------------------------------------------------
1 | import Replicate from "replicate";
2 | import { auth } from "@clerk/nextjs";
3 | import { NextResponse } from "next/server";
4 |
5 | import { incrementApiLimit, checkApiLimit } from "@/lib/api-limit";
6 | import { checkSubscription } from "@/lib/subscription";
7 |
8 | const replicate = new Replicate({
9 | auth: process.env.REPLICATE_API_TOKEN!,
10 | });
11 |
12 | export async function POST(
13 | req: Request
14 | ) {
15 | try {
16 | const { userId } = auth();
17 | const body = await req.json();
18 | const { prompt } = body;
19 |
20 | if (!userId) {
21 | return new NextResponse("Unauthorized", { status: 401 });
22 | }
23 |
24 | if (!prompt) {
25 | return new NextResponse("Prompt is required", { status: 400 });
26 | }
27 |
28 | const freeTrial = await checkApiLimit();
29 | const isPro = await checkSubscription();
30 |
31 | if (!freeTrial && !isPro) {
32 | return new NextResponse("Free trial has expired. Please upgrade to pro.", { status: 403 });
33 | }
34 |
35 | const response = await replicate.run(
36 | "riffusion/riffusion:8cf61ea6c56afd61d8f5b9ffd14d7c216c0a93844ce2d82ac1c9ecc9c7f24e05",
37 | {
38 | input: {
39 | prompt_a: prompt
40 | }
41 | }
42 | );
43 |
44 | if (!isPro) {
45 | await incrementApiLimit();
46 | }
47 |
48 | return NextResponse.json(response);
49 | } catch (error) {
50 | console.log('[MUSIC_ERROR]', error);
51 | return new NextResponse("Internal Error", { status: 500 });
52 | }
53 | };
54 |
--------------------------------------------------------------------------------
/app/api/video/route.ts:
--------------------------------------------------------------------------------
1 | import Replicate from "replicate";
2 | import { auth } from "@clerk/nextjs";
3 | import { NextResponse } from "next/server";
4 |
5 | import { incrementApiLimit, checkApiLimit } from "@/lib/api-limit";
6 | import { checkSubscription } from "@/lib/subscription";
7 |
8 | const replicate = new Replicate({
9 | auth: process.env.REPLICATE_API_TOKEN!,
10 | });
11 |
12 | export async function POST(
13 | req: Request
14 | ) {
15 | try {
16 | const { userId } = auth();
17 | const body = await req.json();
18 | const { prompt } = body;
19 |
20 | if (!userId) {
21 | return new NextResponse("Unauthorized", { status: 401 });
22 | }
23 |
24 | if (!prompt) {
25 | return new NextResponse("Prompt is required", { status: 400 });
26 | }
27 |
28 | const freeTrial = await checkApiLimit();
29 | const isPro = await checkSubscription();
30 |
31 | if (!freeTrial && !isPro) {
32 | return new NextResponse("Free trial has expired. Please upgrade to pro.", { status: 403 });
33 | }
34 |
35 | const response = await replicate.run(
36 | "anotherjesse/zeroscope-v2-xl:71996d331e8ede8ef7bd76eba9fae076d31792e4ddf4ad057779b443d6aea62f",
37 | {
38 | input: {
39 | prompt,
40 | }
41 | }
42 | );
43 |
44 | if (!isPro) {
45 | await incrementApiLimit();
46 | }
47 |
48 | return NextResponse.json(response);
49 | } catch (error) {
50 | console.log('[VIDEO_ERROR]', error);
51 | return new NextResponse("Internal Error", { status: 500 });
52 | }
53 | };
54 |
--------------------------------------------------------------------------------
/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/moddle-toggle.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { MoonIcon, SunIcon } from "lucide-react"
5 | import { useTheme } from "next-themes"
6 |
7 | import { Button } from "@/components/ui/button"
8 | import {
9 | DropdownMenu,
10 | DropdownMenuContent,
11 | DropdownMenuItem,
12 | DropdownMenuTrigger,
13 | } from "@/components/ui/dropdown-menu"
14 |
15 | export function ModeToggle() {
16 | const { setTheme } = useTheme()
17 |
18 | return (
19 |
20 |
21 |
26 |
27 |
28 | setTheme("light")}>
29 | Light
30 |
31 | setTheme("dark")}>
32 | Dark
33 |
34 | setTheme("system")}>
35 | System
36 |
37 |
38 |
39 | )
40 | }
41 |
--------------------------------------------------------------------------------
/components/landing-hero.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import TypewriterComponent from "typewriter-effect";
4 | import Link from "next/link";
5 | import { useAuth } from "@clerk/nextjs";
6 |
7 | import { Button } from "@/components/ui/button";
8 |
9 | export const LandingHero = () => {
10 | const { isSignedIn } = useAuth();
11 |
12 | return (
13 |
14 |
15 |
The Best AI Tool for
16 |
17 |
29 |
30 |
31 |
32 | Create content using AI 10x faster.
33 |
34 |
35 |
36 |
39 |
40 |
41 |
42 | No credit card required.
43 |
44 |
45 | );
46 | };
--------------------------------------------------------------------------------
/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 { checkSubscription } from "@/lib/subscription";
6 | import { incrementApiLimit, checkApiLimit } from "@/lib/api-limit";
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(
15 | req: Request
16 | ) {
17 | try {
18 | const { userId } = auth();
19 | const body = await req.json();
20 | const { messages } = body;
21 |
22 | if (!userId) {
23 | return new NextResponse("Unauthorized", { status: 401 });
24 | }
25 |
26 | if (!configuration.apiKey) {
27 | return new NextResponse("OpenAI API Key not configured.", { status: 500 });
28 | }
29 |
30 | if (!messages) {
31 | return new NextResponse("Messages are required", { status: 400 });
32 | }
33 |
34 | const freeTrial = await checkApiLimit();
35 | const isPro = await checkSubscription();
36 |
37 | if (!freeTrial && !isPro) {
38 | return new NextResponse("Free trial has expired. Please upgrade to pro.", { status: 403 });
39 | }
40 |
41 | const response = await openai.createChatCompletion({
42 | model: "gpt-3.5-turbo",
43 | messages
44 | });
45 |
46 | if (!isPro) {
47 | await incrementApiLimit();
48 | }
49 |
50 | return NextResponse.json(response.data.choices[0].message);
51 | } catch (error) {
52 | console.log('[CONVERSATION_ERROR]', error);
53 | return new NextResponse("Internal Error", { status: 500 });
54 | }
55 | };
56 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/dashboard/page.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 | import { ArrowRight } from "lucide-react";
3 | import { tools } from "@/constant";
4 | import { Card } from "@/components/ui/card";
5 | import { cn } from "@/lib/utils";
6 | import { useRouter } from "next/navigation";
7 | export default function Dashboard() {
8 | const router = useRouter()
9 | return (
10 |
11 |
12 |
13 | Explore the power of AI
14 |
15 |
16 | Chat with the smartest AI - Experience the power of AI
17 |
18 |
19 |
20 | {tools.map((tool) => (
21 |
router.push(tool.href)} key={tool.href} className="p-4 border-black/5 flex items-center justify-between hover:shadow-md transition cursor-pointer">
22 |
23 |
24 |
25 |
26 |
27 | {tool.label}
28 |
29 |
30 |
31 |
32 | ))}
33 |
34 |
35 |
36 | )
37 | }
--------------------------------------------------------------------------------
/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 | "postinstall": "prisma generate"
11 | },
12 | "dependencies": {
13 | "@clerk/nextjs": "^4.24.1",
14 | "@hookform/resolvers": "^3.3.1",
15 | "@prisma/client": "^5.4.1",
16 | "@radix-ui/react-avatar": "^1.0.4",
17 | "@radix-ui/react-dialog": "^1.0.4",
18 | "@radix-ui/react-dropdown-menu": "^2.0.6",
19 | "@radix-ui/react-label": "^2.0.2",
20 | "@radix-ui/react-progress": "^1.0.3",
21 | "@radix-ui/react-select": "^2.0.0",
22 | "@radix-ui/react-separator": "^1.0.3",
23 | "@radix-ui/react-slot": "^1.0.2",
24 | "@types/node": "20.6.5",
25 | "@types/react": "18.2.22",
26 | "@types/react-dom": "18.2.7",
27 | "autoprefixer": "10.4.16",
28 | "axios": "^1.5.1",
29 | "class-variance-authority": "^0.7.0",
30 | "clsx": "^2.0.0",
31 | "cmdk": "^0.2.0",
32 | "crisp-sdk-web": "^1.0.21",
33 | "eslint": "8.50.0",
34 | "eslint-config-next": "13.5.2",
35 | "lucide-react": "^0.279.0",
36 | "next": "13.5.2",
37 | "next-themes": "^0.2.1",
38 | "openai": "3.3.0",
39 | "postcss": "8.4.30",
40 | "prisma": "^5.4.1",
41 | "react": "18.2.0",
42 | "react-dom": "18.2.0",
43 | "react-hook-form": "^7.46.2",
44 | "react-hot-toast": "^2.4.1",
45 | "react-markdown": "^8.0.7",
46 | "replicate": "^0.18.1",
47 | "stripe": "^13.8.0",
48 | "tailwind-merge": "^1.14.0",
49 | "tailwindcss": "3.3.3",
50 | "tailwindcss-animate": "^1.0.7",
51 | "typescript": "5.2.2",
52 | "typewriter-effect": "^2.21.0",
53 | "zod": "^3.22.2",
54 | "zustand": "^4.4.1"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | html,body {
6 | height:100%;
7 | }
8 |
9 | @layer base {
10 | :root {
11 | --background: 0 0% 100%;
12 | --foreground: 222.2 84% 4.9%;
13 |
14 | --card: 0 0% 100%;
15 | --card-foreground: 222.2 84% 4.9%;
16 |
17 | --popover: 0 0% 100%;
18 | --popover-foreground: 222.2 84% 4.9%;
19 |
20 | --primary: 222.2 47.4% 11.2%;
21 | --primary-foreground: 210 40% 98%;
22 |
23 | --secondary: 210 40% 96.1%;
24 | --secondary-foreground: 222.2 47.4% 11.2%;
25 |
26 | --muted: 210 40% 96.1%;
27 | --muted-foreground: 215.4 16.3% 46.9%;
28 |
29 | --accent: 210 40% 96.1%;
30 | --accent-foreground: 222.2 47.4% 11.2%;
31 |
32 | --destructive: 0 84.2% 60.2%;
33 | --destructive-foreground: 210 40% 98%;
34 |
35 | --border: 214.3 31.8% 91.4%;
36 | --input: 214.3 31.8% 91.4%;
37 | --ring: 222.2 84% 4.9%;
38 |
39 | --radius: 0.5rem;
40 | }
41 |
42 | .dark {
43 | --background: 222.2 84% 4.9%;
44 | --foreground: 210 40% 98%;
45 |
46 | --card: 222.2 84% 4.9%;
47 | --card-foreground: 210 40% 98%;
48 |
49 | --popover: 222.2 84% 4.9%;
50 | --popover-foreground: 210 40% 98%;
51 |
52 | --primary: 210 40% 98%;
53 | --primary-foreground: 222.2 47.4% 11.2%;
54 |
55 | --secondary: 217.2 32.6% 17.5%;
56 | --secondary-foreground: 210 40% 98%;
57 |
58 | --muted: 217.2 32.6% 17.5%;
59 | --muted-foreground: 215 20.2% 65.1%;
60 |
61 | --accent: 217.2 32.6% 17.5%;
62 | --accent-foreground: 210 40% 98%;
63 |
64 | --destructive: 0 62.8% 30.6%;
65 | --destructive-foreground: 210 40% 98%;
66 |
67 | --border: 217.2 32.6% 17.5%;
68 | --input: 217.2 32.6% 17.5%;
69 | --ring: 212.7 26.8% 83.9%;
70 | }
71 | }
72 |
73 | @layer base {
74 | * {
75 | @apply border-border;
76 | }
77 | body {
78 | @apply bg-background text-foreground;
79 | }
80 | }
--------------------------------------------------------------------------------
/app/api/image/route.ts:
--------------------------------------------------------------------------------
1 | import { auth } from "@clerk/nextjs";
2 | import { NextResponse } from "next/server";
3 | import { Configuration, OpenAIApi } from "openai";
4 |
5 | import { checkSubscription } from "@/lib/subscription";
6 | import { incrementApiLimit, checkApiLimit } from "@/lib/api-limit";
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(
15 | req: Request
16 | ) {
17 | try {
18 | const { userId } = auth();
19 | const body = await req.json();
20 | const { prompt, amount = 1, resolution = "512x512" } = body;
21 |
22 | if (!userId) {
23 | return new NextResponse("Unauthorized", { status: 401 });
24 | }
25 |
26 | if (!configuration.apiKey) {
27 | return new NextResponse("OpenAI API Key not configured.", { status: 500 });
28 | }
29 |
30 | if (!prompt) {
31 | return new NextResponse("Prompt is required", { status: 400 });
32 | }
33 |
34 | if (!amount) {
35 | return new NextResponse("Amount is required", { status: 400 });
36 | }
37 |
38 | if (!resolution) {
39 | return new NextResponse("Resolution is required", { status: 400 });
40 | }
41 |
42 | const freeTrial = await checkApiLimit();
43 | const isPro = await checkSubscription();
44 |
45 | if (!freeTrial && !isPro) {
46 | return new NextResponse("Free trial has expired. Please upgrade to pro.", { status: 403 });
47 | }
48 |
49 | const response = await openai.createImage({
50 | prompt,
51 | n: parseInt(amount, 10),
52 | size: resolution,
53 | });
54 |
55 | if (!isPro) {
56 | await incrementApiLimit();
57 | }
58 |
59 | return NextResponse.json(response.data.data);
60 | } catch (error) {
61 | console.log('[IMAGE_ERROR]', error);
62 | return new NextResponse("Internal Error", { status: 500 });
63 | }
64 | };
65 |
--------------------------------------------------------------------------------
/app/api/code/route.ts:
--------------------------------------------------------------------------------
1 | import { auth } from "@clerk/nextjs";
2 | import { NextResponse } from "next/server";
3 | import { ChatCompletionRequestMessage, Configuration, OpenAIApi } from "openai";
4 |
5 | import { checkSubscription } from "@/lib/subscription";
6 | import { incrementApiLimit, checkApiLimit } from "@/lib/api-limit";
7 |
8 | const configuration = new Configuration({
9 | apiKey: process.env.OPENAI_API_KEY,
10 | });
11 |
12 | const openai = new OpenAIApi(configuration);
13 |
14 | const instructionMessage: ChatCompletionRequestMessage = {
15 | role: "system",
16 | content: "You are a code generator. You must answer only in markdown code snippets. Use code comments for explanations."
17 | };
18 |
19 | export async function POST(
20 | req: Request
21 | ) {
22 | try {
23 | const { userId } = auth();
24 | const body = await req.json();
25 | const { messages } = body;
26 |
27 | if (!userId) {
28 | return new NextResponse("Unauthorized", { status: 401 });
29 | }
30 |
31 | if (!configuration.apiKey) {
32 | return new NextResponse("OpenAI API Key not configured.", { status: 500 });
33 | }
34 |
35 | if (!messages) {
36 | return new NextResponse("Messages are required", { status: 400 });
37 | }
38 |
39 | const freeTrial = await checkApiLimit();
40 | const isPro = await checkSubscription();
41 |
42 | if (!freeTrial && !isPro) {
43 | return new NextResponse("Free trial has expired. Please upgrade to pro.", { status: 403 });
44 | }
45 |
46 | const response = await openai.createChatCompletion({
47 | model: "gpt-3.5-turbo",
48 | messages: [instructionMessage, ...messages]
49 | });
50 |
51 | if (!isPro) {
52 | await incrementApiLimit();
53 | }
54 |
55 | return NextResponse.json(response.data.choices[0].message);
56 | } catch (error) {
57 | console.log('[CODE_ERROR]', error);
58 | return new NextResponse("Internal Error", { status: 500 });
59 | }
60 | };
61 |
--------------------------------------------------------------------------------
/components/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react"
2 | import { Slot } from "@radix-ui/react-slot"
3 | import { cva, type VariantProps } from "class-variance-authority"
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:
14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15 | outline:
16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17 | secondary:
18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19 | ghost: "hover:bg-accent hover:text-accent-foreground",
20 | link: "text-primary underline-offset-4 hover:underline",
21 | premium: "bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 text-white border-0",
22 | },
23 | size: {
24 | default: "h-10 px-4 py-2",
25 | sm: "h-9 rounded-md px-3",
26 | lg: "h-11 rounded-md px-8",
27 | icon: "h-10 w-10",
28 | },
29 | },
30 | defaultVariants: {
31 | variant: "default",
32 | size: "default",
33 | },
34 | }
35 | )
36 |
37 | export interface ButtonProps
38 | extends React.ButtonHTMLAttributes,
39 | VariantProps {
40 | asChild?: boolean
41 | }
42 |
43 | const Button = React.forwardRef(
44 | ({ className, variant, size, asChild = false, ...props }, ref) => {
45 | const Comp = asChild ? Slot : "button"
46 | return (
47 |
52 | )
53 | }
54 | )
55 | Button.displayName = "Button"
56 |
57 | export { Button, buttonVariants }
58 |
--------------------------------------------------------------------------------
/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 }))
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: "Genius Pro",
47 | description: "Unlimited AI Generations"
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 }))
63 | } catch (error) {
64 | console.log("[STRIPE_ERROR]", error);
65 | return new NextResponse("Internal Error", { status: 500 });
66 | }
67 | };
--------------------------------------------------------------------------------
/app/api/webhook/route.ts:
--------------------------------------------------------------------------------
1 | import Stripe from "stripe"
2 | import { headers } from "next/headers"
3 | import { NextResponse } from "next/server"
4 |
5 | import prismadb from "@/lib/prismadb"
6 | import { stripe } from "@/lib/stripe"
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(
16 | body,
17 | signature,
18 | process.env.STRIPE_WEBHOOK_SECRET!
19 | )
20 | } catch (error: any) {
21 | return new NextResponse(`Webhook Error: ${error.message}`, { status: 400 })
22 | }
23 |
24 | const session = event.data.object as Stripe.Checkout.Session
25 |
26 | if (event.type === "checkout.session.completed") {
27 | const subscription = await stripe.subscriptions.retrieve(
28 | session.subscription as string
29 | )
30 |
31 | if (!session?.metadata?.userId) {
32 | return new NextResponse("User id is required", { status: 400 });
33 | }
34 |
35 | await prismadb.userSubscription.create({
36 | data: {
37 | userId: session?.metadata?.userId,
38 | stripeSubscriptionId: subscription.id,
39 | stripeCustomerId: subscription.customer as string,
40 | stripePriceId: subscription.items.data[0].price.id,
41 | stripeCurrentPeriodEnd: new Date(
42 | subscription.current_period_end * 1000
43 | ),
44 | },
45 | })
46 | }
47 |
48 | if (event.type === "invoice.payment_succeeded") {
49 | const subscription = await stripe.subscriptions.retrieve(
50 | session.subscription as string
51 | )
52 |
53 | await prismadb.userSubscription.update({
54 | where: {
55 | stripeSubscriptionId: subscription.id,
56 | },
57 | data: {
58 | stripePriceId: subscription.items.data[0].price.id,
59 | stripeCurrentPeriodEnd: new Date(
60 | subscription.current_period_end * 1000
61 | ),
62 | },
63 | })
64 | }
65 |
66 | return new NextResponse(null, { status: 200 })
67 | };
68 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/components/pro-modal.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { useState } from "react";
4 | import { Check, Zap } from "lucide-react";
5 | import { toast } from "react-hot-toast";
6 | import axios from "axios";
7 |
8 | import {
9 | Dialog,
10 | DialogContent,
11 | DialogHeader,
12 | DialogTitle,
13 | DialogDescription,
14 | DialogFooter
15 | } from "@/components/ui/dialog";
16 | import { Badge } from "@/components/ui/badge";
17 | import { Button } from "@/components/ui/button";
18 | import { useProModal } from "@/hooks/use-pro-modal";
19 | import { tools } from "@/constant";
20 | import { Card } from "@/components/ui/card";
21 | import { cn } from "@/lib/utils";
22 |
23 | export const ProModal = () => {
24 | const proModal = useProModal();
25 | const [loading, setLoading] = useState(false);
26 |
27 | const onSubscribe = async () => {
28 | try {
29 | setLoading(true);
30 | const response = await axios.get("/api/stripe");
31 |
32 | window.location.href = response.data.url;
33 | } catch (error) {
34 | toast.error("Something went wrong");
35 | } finally {
36 | setLoading(false);
37 | }
38 | }
39 |
40 | return (
41 |
76 | );
77 | };
--------------------------------------------------------------------------------
/components/sidebar.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import Link from "next/link";
4 | import Image from "next/image";
5 | import { Montserrat } from 'next/font/google'
6 | import { Code, ImageIcon, LayoutDashboard, MessageSquare, Music, Settings, VideoIcon } from "lucide-react";
7 | import { usePathname } from "next/navigation";
8 |
9 | import { cn } from "@/lib/utils";
10 | import { FreeCounter } from "@/components/free-counter";
11 |
12 | const poppins = Montserrat({ weight: '600', subsets: ['latin'] });
13 |
14 | const routes = [
15 | {
16 | label: 'Dashboard',
17 | icon: LayoutDashboard,
18 | href: '/dashboard',
19 | color: "text-sky-500"
20 | },
21 | {
22 | label: 'Conversation',
23 | icon: MessageSquare,
24 | href: '/conversation',
25 | color: "text-violet-500",
26 | },
27 | {
28 | label: 'Image Generation',
29 | icon: ImageIcon,
30 | color: "text-pink-700",
31 | href: '/image',
32 | },
33 | {
34 | label: 'Video Generation',
35 | icon: VideoIcon,
36 | color: "text-orange-700",
37 | href: '/video',
38 | },
39 | {
40 | label: 'Music Generation',
41 | icon: Music,
42 | color: "text-emerald-500",
43 | href: '/music',
44 | },
45 | {
46 | label: 'Code Generation',
47 | icon: Code,
48 | color: "text-green-700",
49 | href: '/code',
50 | },
51 | {
52 | label: 'Settings',
53 | icon: Settings,
54 | href: '/settings',
55 | },
56 | ];
57 |
58 | const Sidebar = ({
59 | apiLimitCount = 0,
60 | isPro = false
61 | }: {
62 | apiLimitCount: number;
63 | isPro: boolean;
64 | }) => {
65 | const pathname = usePathname();
66 |
67 | return (
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | Genius
76 |
77 |
78 |
79 | {routes.map((route) => (
80 |
88 |
89 |
90 | {route.label}
91 |
92 |
93 | ))}
94 |
95 |
96 |
100 |
101 | );
102 | };
103 |
104 | export default Sidebar;
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/music/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as z from "zod";
4 | import axios from "axios";
5 | import { useState } from "react";
6 | import { zodResolver } from "@hookform/resolvers/zod";
7 | import { useForm } from "react-hook-form";
8 | import { toast } from "react-hot-toast";
9 | import { useRouter } from "next/navigation";
10 | import { Music, Send } from "lucide-react";
11 |
12 | import { Heading } from "@/components/heading";
13 | import { Button } from "@/components/ui/button";
14 | import { Input } from "@/components/ui/input";
15 | import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
16 | import { Loader } from "@/components/loader";
17 | import Empty from "@/components/empty";
18 |
19 | import { formSchema } from "./constant";
20 | import { useProModal } from "@/hooks/use-pro-modal";
21 |
22 | const MusicPage = () => {
23 | const proModal = useProModal();
24 | const router = useRouter();
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 | try {
38 | setMusic(undefined);
39 |
40 | const response = await axios.post('/api/music', values);
41 | console.log(response)
42 |
43 | setMusic(response.data.audio);
44 | form.reset();
45 | } catch (error: any) {
46 | if (error?.response?.status === 403) {
47 | proModal.onOpen();
48 | }
49 | else {
50 | toast.error("Something went wrong.");
51 | }
52 | } finally {
53 | router.refresh();
54 | }
55 | }
56 |
57 | return (
58 |
59 |
66 |
67 |
102 |
103 | {isLoading && (
104 |
105 |
106 |
107 | )}
108 | {!music && !isLoading && (
109 |
110 | )}
111 | {music && (
112 |
115 | )}
116 |
117 |
118 | );
119 | }
120 |
121 | export default MusicPage;
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/video/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as z from "zod";
4 | import axios from "axios";
5 | import { useState } from "react";
6 | import { zodResolver } from "@hookform/resolvers/zod";
7 | import { useForm } from "react-hook-form";
8 | import { toast } from "react-hot-toast";
9 | import { FileAudio } from "lucide-react";
10 | import { useRouter } from "next/navigation";
11 |
12 | import { Heading } from "@/components/heading";
13 | import { Button } from "@/components/ui/button";
14 | import { Input } from "@/components/ui/input";
15 | import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
16 | import { Loader } from "@/components/loader";
17 | import Empty from "@/components/empty";
18 |
19 | import { formSchema } from "./constant";
20 | import { useProModal } from "@/hooks/use-pro-modal";
21 |
22 | const VideoPage = () => {
23 | const proModal = useProModal();
24 | const router = useRouter();
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 | try {
38 | setVideo(undefined);
39 |
40 | const response = await axios.post('/api/video', values);
41 |
42 | setVideo(response.data[0]);
43 | form.reset();
44 | } catch (error: any) {
45 | if (error?.response?.status === 403) {
46 | proModal.onOpen();
47 | } else {
48 | toast.error("Something went wrong.");
49 | }
50 | } finally {
51 | router.refresh();
52 | }
53 | }
54 |
55 | return (
56 |
57 |
64 |
65 |
100 |
101 | {isLoading && (
102 |
103 |
104 |
105 | )}
106 | {!video && !isLoading && (
107 |
108 | )}
109 | {video && (
110 |
113 | )}
114 |
115 |
116 | );
117 | }
118 |
119 | export default VideoPage;
--------------------------------------------------------------------------------
/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/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/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/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/ui/command.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import { DialogProps } from "@radix-ui/react-dialog"
5 | import { Command as CommandPrimitive } from "cmdk"
6 | import { Search } from "lucide-react"
7 |
8 | import { cn } from "@/lib/utils"
9 | import { Dialog, DialogContent } from "@/components/ui/dialog"
10 |
11 | const Command = React.forwardRef<
12 | React.ElementRef,
13 | React.ComponentPropsWithoutRef
14 | >(({ className, ...props }, ref) => (
15 |
23 | ))
24 | Command.displayName = CommandPrimitive.displayName
25 |
26 | interface CommandDialogProps extends DialogProps {}
27 |
28 | const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
29 | return (
30 |
37 | )
38 | }
39 |
40 | const CommandInput = React.forwardRef<
41 | React.ElementRef,
42 | React.ComponentPropsWithoutRef
43 | >(({ className, ...props }, ref) => (
44 |
45 |
46 |
54 |
55 | ))
56 |
57 | CommandInput.displayName = CommandPrimitive.Input.displayName
58 |
59 | const CommandList = React.forwardRef<
60 | React.ElementRef,
61 | React.ComponentPropsWithoutRef
62 | >(({ className, ...props }, ref) => (
63 |
68 | ))
69 |
70 | CommandList.displayName = CommandPrimitive.List.displayName
71 |
72 | const CommandEmpty = React.forwardRef<
73 | React.ElementRef,
74 | React.ComponentPropsWithoutRef
75 | >((props, ref) => (
76 |
81 | ))
82 |
83 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName
84 |
85 | const CommandGroup = React.forwardRef<
86 | React.ElementRef,
87 | React.ComponentPropsWithoutRef
88 | >(({ className, ...props }, ref) => (
89 |
97 | ))
98 |
99 | CommandGroup.displayName = CommandPrimitive.Group.displayName
100 |
101 | const CommandSeparator = React.forwardRef<
102 | React.ElementRef,
103 | React.ComponentPropsWithoutRef
104 | >(({ className, ...props }, ref) => (
105 |
110 | ))
111 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName
112 |
113 | const CommandItem = React.forwardRef<
114 | React.ElementRef,
115 | React.ComponentPropsWithoutRef
116 | >(({ className, ...props }, ref) => (
117 |
125 | ))
126 |
127 | CommandItem.displayName = CommandPrimitive.Item.displayName
128 |
129 | const CommandShortcut = ({
130 | className,
131 | ...props
132 | }: React.HTMLAttributes) => {
133 | return (
134 |
141 | )
142 | }
143 | CommandShortcut.displayName = "CommandShortcut"
144 |
145 | export {
146 | Command,
147 | CommandDialog,
148 | CommandInput,
149 | CommandList,
150 | CommandEmpty,
151 | CommandGroup,
152 | CommandItem,
153 | CommandShortcut,
154 | CommandSeparator,
155 | }
156 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/conversation/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Heading } from "@/components/heading";
3 | import { ChatCompletionRequestMessage } from "openai";
4 | import { useState } from "react";
5 | import axios from "axios";
6 | import { useRouter } from "next/navigation";
7 | import { MessageSquare } from "lucide-react";
8 | import * as z from "zod";
9 | import { useForm } from "react-hook-form";
10 | import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
11 | import { zodResolver } from "@hookform/resolvers/zod";
12 | import { formSchema } from "./constant";
13 | import { Button } from "@/components/ui/button";
14 | import { Input } from "@/components/ui/input";
15 | import Empty from "@/components/empty";
16 | import { Loader } from "@/components/loader";
17 | import UserAvatar from "@/components/user-avatar";
18 | import { cn } from "@/lib/utils";
19 | import { BotAvatar } from "@/components/bot-avatar";
20 | import { useProModal } from "@/hooks/use-pro-modal";
21 | import toast from "react-hot-toast";
22 | const ConversationPage = () => {
23 | const ProModal = useProModal();
24 | const router = useRouter();
25 | const [messages, setMessages] = useState([]);
26 | const form = useForm>({
27 | resolver: zodResolver(formSchema),
28 | defaultValues: {
29 | prompt: ""
30 | }
31 | });
32 | const isLoading = form.formState.isSubmitting;
33 | const onSubmit = async (values: z.infer) => {
34 | try {
35 | const userMessage: ChatCompletionRequestMessage = { role: "user", content: values.prompt };
36 | const newMessages = [...messages, userMessage];
37 | const response = await axios.post('/api/conversation', { messages: newMessages });
38 | setMessages((current) => [...current, userMessage, response.data]);
39 | form.reset();
40 | } catch (err:any) {
41 | if(err?.response?.status === 403) {
42 | ProModal.onOpen();
43 | }else{
44 | toast.error("Something went wrong.");
45 | }
46 | } finally {
47 | router.refresh()
48 | }
49 | }
50 | return (
51 |
52 |
59 |
60 |
95 |
96 |
97 |
98 | {isLoading && (
99 |
100 |
101 |
102 | )}
103 | {messages.length === 0 && !isLoading && (
104 |
105 | )}
106 |
107 | {messages.map((message) => (
108 |
115 | {message.role === "user" ?
:
}
116 |
117 | {message.content}
118 |
119 |
120 | ))}
121 |
122 |
123 |
124 | );
125 | }
126 |
127 | export default ConversationPage;
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/code/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 | import { Heading } from "@/components/heading";
3 | import { ChatCompletionRequestMessage } from "openai";
4 | import { useState } from "react";
5 | import axios from "axios";
6 | import { useRouter } from "next/navigation";
7 | import { Code, MessageSquare } from "lucide-react";
8 | import * as z from "zod";
9 | import { useForm } from "react-hook-form";
10 | import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
11 | import { zodResolver } from "@hookform/resolvers/zod";
12 | import { formSchema } from "./constant";
13 | import { Button } from "@/components/ui/button";
14 | import { Input } from "@/components/ui/input";
15 | import Empty from "@/components/empty";
16 | import { Loader } from "@/components/loader";
17 | import UserAvatar from "@/components/user-avatar";
18 | import { cn } from "@/lib/utils";
19 | import { BotAvatar } from "@/components/bot-avatar";
20 | import ReactMarkdown from "react-markdown";
21 | import { useProModal } from "@/hooks/use-pro-modal";
22 | import toast from "react-hot-toast";
23 | const CodePage = () => {
24 | const router = useRouter();
25 | const proModal = useProModal();
26 | const [messages, setMessages] = useState([]);
27 | const form = useForm>({
28 | resolver: zodResolver(formSchema),
29 | defaultValues: {
30 | prompt: ""
31 | }
32 | });
33 |
34 | const isLoading = form.formState.isSubmitting;
35 | const onSubmit = async (values: z.infer) => {
36 | try {
37 | const userMessage: ChatCompletionRequestMessage = { role: "user", content: values.prompt };
38 | const newMessages = [...messages, userMessage];
39 | console.log(newMessages);
40 | const response = await axios.post('/api/code', { messages: newMessages });
41 | setMessages((current) => [...current, userMessage, response.data]);
42 |
43 | form.reset();
44 | } catch (err:any) {
45 | if(err?.response?.status === 403) {
46 | proModal.onOpen();
47 | }else{
48 | toast.error("Something went wrong.");
49 | }
50 | } finally {
51 | router.refresh()
52 | }
53 | }
54 | return (
55 |
56 |
63 |
64 |
99 |
100 |
101 |
102 | {isLoading && (
103 |
104 |
105 |
106 | )}
107 | {messages.length === 0 && !isLoading && (
108 |
109 | )}
110 |
111 | {messages.map((message) => (
112 |
119 | {message.role === "user" ?
:
}
120 |
(
122 |
125 | ),
126 | code: ({ node, ...props }) => (
127 |
128 | )
129 | }} className="text-sm overflow-hidden leading-7">
130 | {message.content || ""}
131 |
132 |
133 | ))}
134 |
135 |
136 |
137 | );
138 | }
139 |
140 | export default CodePage;
--------------------------------------------------------------------------------
/components/ui/dropdown-menu.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as React from "react"
4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
5 | import { Check, ChevronRight, Circle } from "lucide-react"
6 |
7 | import { cn } from "@/lib/utils"
8 |
9 | const DropdownMenu = DropdownMenuPrimitive.Root
10 |
11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
12 |
13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group
14 |
15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal
16 |
17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub
18 |
19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
20 |
21 | const DropdownMenuSubTrigger = React.forwardRef<
22 | React.ElementRef,
23 | React.ComponentPropsWithoutRef & {
24 | inset?: boolean
25 | }
26 | >(({ className, inset, children, ...props }, ref) => (
27 |
36 | {children}
37 |
38 |
39 | ))
40 | DropdownMenuSubTrigger.displayName =
41 | DropdownMenuPrimitive.SubTrigger.displayName
42 |
43 | const DropdownMenuSubContent = React.forwardRef<
44 | React.ElementRef,
45 | React.ComponentPropsWithoutRef
46 | >(({ className, ...props }, ref) => (
47 |
55 | ))
56 | DropdownMenuSubContent.displayName =
57 | DropdownMenuPrimitive.SubContent.displayName
58 |
59 | const DropdownMenuContent = React.forwardRef<
60 | React.ElementRef,
61 | React.ComponentPropsWithoutRef
62 | >(({ className, sideOffset = 4, ...props }, ref) => (
63 |
64 |
73 |
74 | ))
75 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
76 |
77 | const DropdownMenuItem = React.forwardRef<
78 | React.ElementRef,
79 | React.ComponentPropsWithoutRef & {
80 | inset?: boolean
81 | }
82 | >(({ className, inset, ...props }, ref) => (
83 |
92 | ))
93 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
94 |
95 | const DropdownMenuCheckboxItem = React.forwardRef<
96 | React.ElementRef,
97 | React.ComponentPropsWithoutRef
98 | >(({ className, children, checked, ...props }, ref) => (
99 |
108 |
109 |
110 |
111 |
112 |
113 | {children}
114 |
115 | ))
116 | DropdownMenuCheckboxItem.displayName =
117 | DropdownMenuPrimitive.CheckboxItem.displayName
118 |
119 | const DropdownMenuRadioItem = React.forwardRef<
120 | React.ElementRef,
121 | React.ComponentPropsWithoutRef
122 | >(({ className, children, ...props }, ref) => (
123 |
131 |
132 |
133 |
134 |
135 |
136 | {children}
137 |
138 | ))
139 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
140 |
141 | const DropdownMenuLabel = React.forwardRef<
142 | React.ElementRef,
143 | React.ComponentPropsWithoutRef & {
144 | inset?: boolean
145 | }
146 | >(({ className, inset, ...props }, ref) => (
147 |
156 | ))
157 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
158 |
159 | const DropdownMenuSeparator = React.forwardRef<
160 | React.ElementRef,
161 | React.ComponentPropsWithoutRef
162 | >(({ className, ...props }, ref) => (
163 |
168 | ))
169 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
170 |
171 | const DropdownMenuShortcut = ({
172 | className,
173 | ...props
174 | }: React.HTMLAttributes) => {
175 | return (
176 |
180 | )
181 | }
182 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
183 |
184 | export {
185 | DropdownMenu,
186 | DropdownMenuTrigger,
187 | DropdownMenuContent,
188 | DropdownMenuItem,
189 | DropdownMenuCheckboxItem,
190 | DropdownMenuRadioItem,
191 | DropdownMenuLabel,
192 | DropdownMenuSeparator,
193 | DropdownMenuShortcut,
194 | DropdownMenuGroup,
195 | DropdownMenuPortal,
196 | DropdownMenuSub,
197 | DropdownMenuSubContent,
198 | DropdownMenuSubTrigger,
199 | DropdownMenuRadioGroup,
200 | }
201 |
--------------------------------------------------------------------------------
/app/(dashboard)/(routes)/image/page.tsx:
--------------------------------------------------------------------------------
1 |
2 |
3 | "use client";
4 |
5 | import * as z from "zod";
6 | import axios from "axios";
7 | import Image from "next/image";
8 | import { useState } from "react";
9 | import { zodResolver } from "@hookform/resolvers/zod";
10 | import { Download, ImageIcon } from "lucide-react";
11 | import { useForm } from "react-hook-form";
12 | import { useRouter } from "next/navigation";
13 |
14 | import { Heading } from "@/components/heading";
15 | import { Button } from "@/components/ui/button";
16 | import { Card, CardFooter } from "@/components/ui/card";
17 | import { Input } from "@/components/ui/input";
18 | import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
19 | import { Loader } from "@/components/loader";
20 | import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
21 | import Empty from "@/components/empty";
22 |
23 | import { amountOptions, formSchema, resolutionOptions } from "./constant";
24 | import { useProModal } from "@/hooks/use-pro-modal";
25 | import toast from "react-hot-toast";
26 |
27 | const PhotoPage = () => {
28 | const router = useRouter();
29 | const proModal = useProModal();
30 | const [photos, setPhotos] = useState([]);
31 |
32 | const form = useForm>({
33 |
34 | resolver: zodResolver(formSchema),
35 | defaultValues: {
36 | prompt: "",
37 | amount: "1",
38 | resolution: "512x512"
39 | }
40 | });
41 |
42 | const isLoading = form.formState.isSubmitting;
43 |
44 | const onSubmit = async (values: z.infer) => {
45 | try {
46 | setPhotos([]);
47 |
48 | const response = await axios.post('/api/image', values);
49 |
50 | const urls = response.data.map((image: { url: string }) => image.url);
51 |
52 | setPhotos(urls);
53 | } catch (error: any) {
54 | if (error?.response?.status === 403) {
55 | proModal.onOpen();
56 | } else{
57 | toast.error("Something went wrong.");
58 | }
59 | } finally {
60 | router.refresh();
61 | }
62 | }
63 |
64 | return (
65 |
66 |
73 |
74 |
169 |
170 | {isLoading && (
171 |
172 |
173 |
174 | )}
175 | {photos.length === 0 && !isLoading && (
176 |
177 | )}
178 |
179 | {photos.map((src) => (
180 |
181 |
182 |
187 |
188 |
189 |
193 |
194 |
195 | ))}
196 |
197 |
198 |
199 | );
200 | }
201 |
202 | export default PhotoPage;
--------------------------------------------------------------------------------