├── .prettierignore ├── commitlintrc.json ├── public ├── empty.png ├── favicon.ico ├── complogo.png ├── ui-screenshot │ ├── pro.png │ ├── login.png │ ├── dashboard.png │ └── landing-page.png ├── vercel.svg ├── next.svg └── technical-support.svg ├── .husky └── pre-commit ├── postcss.config.js ├── app ├── (auth) │ ├── (routes) │ │ ├── sign-in │ │ │ └── [[...sign-in]] │ │ │ │ └── page.tsx │ │ └── sign-up │ │ │ └── [[...sign-up]] │ │ │ └── page.tsx │ └── layout.tsx ├── (dashboard) │ ├── (routes) │ │ ├── chat │ │ │ ├── constants.ts │ │ │ └── page.tsx │ │ ├── code │ │ │ ├── constants.ts │ │ │ └── page.tsx │ │ ├── video │ │ │ ├── constants.ts │ │ │ └── page.tsx │ │ ├── music │ │ │ ├── constants.ts │ │ │ └── page.tsx │ │ ├── image │ │ │ ├── constants.ts │ │ │ └── page.tsx │ │ ├── settings │ │ │ └── page.tsx │ │ └── dashboard │ │ │ └── page.tsx │ └── layout.tsx ├── (landing) │ ├── layout.tsx │ └── page.tsx ├── layout.tsx ├── api │ ├── video │ │ └── route.ts │ ├── music │ │ └── route.ts │ ├── chat │ │ └── route.ts │ ├── image │ │ └── route.ts │ ├── code │ │ └── route.ts │ ├── webhook │ │ └── route.ts │ └── stripe │ │ └── route.ts └── globals.css ├── lib ├── stripe-connect.ts ├── prismadb.ts ├── utils.ts ├── subscription.ts └── api-limit.ts ├── next.config.js ├── .editorconfig ├── components ├── ChatSupportProvider.tsx ├── ToastProvider.tsx ├── BotAvatar.tsx ├── ChatSupport.tsx ├── Loader.tsx ├── ModalProvider.tsx ├── UserAvatar.tsx ├── Empty.tsx ├── Navbar.tsx ├── ui │ ├── label.tsx │ ├── progress.tsx │ ├── input.tsx │ ├── badge.tsx │ ├── avatar.tsx │ ├── button.tsx │ ├── card.tsx │ ├── dialog.tsx │ ├── select.tsx │ ├── form.tsx │ └── sheet.tsx ├── Heading.tsx ├── MobileSidebar.tsx ├── SubscribeButton.tsx ├── LandingNavbar.tsx ├── FreeCounter.tsx ├── LandingHero.tsx ├── LandingPageContents.tsx ├── ProModal.tsx └── Sidebar.tsx ├── docker-compose.yml ├── Dockerfile ├── hooks └── useProModal.tsx ├── components.json ├── .prettierrc.json ├── middleware.ts ├── .gitignore ├── .env.example ├── tsconfig.json ├── .eslintrc.json ├── prisma └── schema.prisma ├── constants.ts ├── .github └── workflows │ └── github-actions.yml ├── contributing.md ├── types └── index.d.ts ├── package.json ├── tailwind.config.js └── README.md /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .next 4 | build 5 | .contentlayer -------------------------------------------------------------------------------- /commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } -------------------------------------------------------------------------------- /public/empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Drakkarrr/D-prompt-AI/HEAD/public/empty.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Drakkarrr/D-prompt-AI/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm test 5 | -------------------------------------------------------------------------------- /public/complogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Drakkarrr/D-prompt-AI/HEAD/public/complogo.png -------------------------------------------------------------------------------- /public/ui-screenshot/pro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Drakkarrr/D-prompt-AI/HEAD/public/ui-screenshot/pro.png -------------------------------------------------------------------------------- /public/ui-screenshot/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Drakkarrr/D-prompt-AI/HEAD/public/ui-screenshot/login.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/ui-screenshot/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Drakkarrr/D-prompt-AI/HEAD/public/ui-screenshot/dashboard.png -------------------------------------------------------------------------------- /public/ui-screenshot/landing-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Drakkarrr/D-prompt-AI/HEAD/public/ui-screenshot/landing-page.png -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/chat/constants.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod' 2 | 3 | export const formSchema = z.object({ 4 | prompt: z.string().min(1, { message: 'Prompt is required' }), 5 | }) -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/code/constants.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod' 2 | 3 | export const formSchema = z.object({ 4 | prompt: z.string().min(1, { message: 'Prompt is required' }), 5 | }) -------------------------------------------------------------------------------- /lib/stripe-connect.ts: -------------------------------------------------------------------------------- 1 | import Stripe from 'stripe' 2 | 3 | export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { 4 | apiVersion: '2022-11-15', 5 | typescript: true, 6 | }) 7 | -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/video/constants.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)/music/constants.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 | }); -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | images: { 4 | domains: ['oaidalleapiprodscus.blob.core.windows.net'], 5 | }, 6 | }; 7 | 8 | module.exports = nextConfig; 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /components/ChatSupportProvider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import ChatSupport from '@/components/ChatSupport'; 4 | 5 | const ChatSupportProvider = () => { 6 | return ; 7 | }; 8 | 9 | export default ChatSupportProvider; 10 | -------------------------------------------------------------------------------- /components/ToastProvider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React from 'react'; 4 | import { Toaster } from 'react-hot-toast'; 5 | 6 | const ToastProvider = () => { 7 | return ; 8 | }; 9 | 10 | export default ToastProvider; 11 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | web: 4 | build: 5 | context: ./ 6 | target: runner 7 | volumes: 8 | - .:/app 9 | command: npm run dev 10 | ports: 11 | - "3000:3000" 12 | environment: 13 | NODE_ENV: development 14 | -------------------------------------------------------------------------------- /lib/prismadb.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from '@prisma/client' 2 | 3 | declare global { 4 | var prisma: PrismaClient | undefined 5 | } 6 | 7 | export const prismadb = global.prisma || new PrismaClient() 8 | if (process.env.NODE_ENV === 'production') global.prisma = prismadb 9 | 10 | -------------------------------------------------------------------------------- /components/BotAvatar.tsx: -------------------------------------------------------------------------------- 1 | import { Avatar, AvatarImage } from '@/components/ui/avatar'; 2 | 3 | const BotAvatar = () => { 4 | return ( 5 | 6 | 7 | 8 | ); 9 | }; 10 | 11 | export default BotAvatar; 12 | -------------------------------------------------------------------------------- /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_HOST}${path}` 10 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine 2 | RUN apk add --no-cache libc6-compat 3 | 4 | WORKDIR /app 5 | COPY package.json yarn.lock ./ 6 | 7 | RUN yarn install 8 | 9 | COPY --from=deps /app/node_modules ./node_modules 10 | COPY . . 11 | 12 | RUN yarn build 13 | EXPOSE 3000 14 | ENV PORT 3000 15 | 16 | CMD ["yarn", "start"] 17 | -------------------------------------------------------------------------------- /app/(auth)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Props } from '@/types'; 2 | 3 | const AuthLayout = ({ children }: Props) => { 4 | return ( 5 |
6 |
{children}
7 |
8 | ); 9 | }; 10 | 11 | export default AuthLayout; 12 | -------------------------------------------------------------------------------- /components/ChatSupport.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useEffect } from 'react'; 4 | import { Crisp } from 'crisp-sdk-web'; 5 | 6 | const ChatSupport = () => { 7 | useEffect(() => { 8 | Crisp.configure(process.env.NEXT_PUBLIC_CRISP_WEBSITE_ID!); 9 | }, []); 10 | 11 | return null; 12 | }; 13 | 14 | export default ChatSupport; 15 | -------------------------------------------------------------------------------- /app/(landing)/layout.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const LandingLayout = ({ children }: { children: React.ReactNode }) => { 4 | return ( 5 |
6 |
{children}
7 |
8 | ); 9 | }; 10 | 11 | export default LandingLayout; 12 | -------------------------------------------------------------------------------- /hooks/useProModal.tsx: -------------------------------------------------------------------------------- 1 | import { create } from 'zustand'; 2 | 3 | interface useProModalStore { 4 | isOpen: boolean; 5 | onOpen: () => void; 6 | onClose: () => void; 7 | } 8 | 9 | export const useProModal = create((set) => ({ 10 | isOpen: false, 11 | onOpen: () => set({ isOpen: true }), 12 | onClose: () => set({ isOpen: false }), 13 | })); 14 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /components/Loader.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | 3 | const Loader = () => { 4 | return ( 5 |
6 |
7 | Logo 8 |
9 |

Generating...

10 |
11 | ); 12 | }; 13 | 14 | export default Loader; 15 | -------------------------------------------------------------------------------- /app/(landing)/page.tsx: -------------------------------------------------------------------------------- 1 | import LandingNavbar from '@/components/LandingNavbar'; 2 | import LandingHero from '@/components/LandingHero'; 3 | // import LandingPageContents from '@/components/LandingPageContents'; 4 | 5 | const LandingPage = () => { 6 | return ( 7 |
8 | 9 | 10 | {/* */} 11 |
12 | ); 13 | }; 14 | 15 | export default LandingPage; 16 | -------------------------------------------------------------------------------- /components/ModalProvider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useEffect, useState } from 'react'; 4 | import ProModal from '@/components/ProModal'; 5 | 6 | const ModalProvider = () => { 7 | const [isMounted, setIsMounted] = useState(false); 8 | 9 | useEffect(() => { 10 | setIsMounted(true); 11 | }, []); 12 | 13 | if (!isMounted) return null; 14 | 15 | return ( 16 | <> 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default ModalProvider; 23 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "semi": true, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "jsxSingleQuote": true, 7 | "plugins": ["prettier-plugin-tailwindcss"], 8 | "endOfLine": "lf", 9 | "importOrderSeparation": false, 10 | "importOrderSortSpecifiers": true, 11 | "importOrderBuiltinModulesToTop": true, 12 | "importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy"], 13 | "importOrderMergeDuplicateImports": true, 14 | "importOrderCombineTypeAndValueImports": true 15 | } 16 | -------------------------------------------------------------------------------- /components/UserAvatar.tsx: -------------------------------------------------------------------------------- 1 | import { useUser } from '@clerk/nextjs'; 2 | 3 | import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; 4 | 5 | const UserAvatar = () => { 6 | const { user } = useUser(); 7 | 8 | return ( 9 | 10 | 11 | 12 | {user?.firstName?.charAt(0)} 13 | {user?.lastName?.charAt(0)} 14 | 15 | 16 | ); 17 | }; 18 | 19 | export default UserAvatar; 20 | -------------------------------------------------------------------------------- /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 | 7 | export default authMiddleware({ 8 | publicRoutes: [ 9 | "/", 10 | '/api/webhook', 11 | ], 12 | }); 13 | 14 | export const config = { 15 | matcher: ["/((?!.*\\..*|_next).*)", "/", "/(api|trpc)(.*)"], 16 | }; 17 | -------------------------------------------------------------------------------- /.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 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | .vercel 39 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/Empty.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image'; 2 | 3 | interface EmptyProps { 4 | label: string; 5 | } 6 | 7 | const Empty: React.FC = ({ label }) => { 8 | return ( 9 |
10 |
11 | Empty placeholder 17 |
18 |

{label}

19 |
20 | ); 21 | }; 22 | 23 | export default Empty; 24 | -------------------------------------------------------------------------------- /components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import { UserButton } from '@clerk/nextjs'; 2 | import MobileSidebar from '@/components/MobileSidebar'; 3 | 4 | import { getApiLimitCount } from '@/lib/api-limit'; 5 | import { getSubscription } from '@/lib/subscription'; 6 | 7 | const Navbar = async () => { 8 | const apiLimit = await getApiLimitCount(); 9 | const isPro = await getSubscription(); 10 | 11 | return ( 12 |
13 | 14 |
15 | 16 |
17 |
18 | ); 19 | }; 20 | 21 | export default Navbar; 22 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | 2 | #! CLERK CONFIGURATION KEYS 3 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= 4 | CLERK_SECRET_KEY= 5 | 6 | NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in 7 | NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up 8 | 9 | NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard 10 | NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard 11 | 12 | #! OPENAI SECRET KEYS 13 | OPENAI_API_KEY= 14 | 15 | #! REPLICATE AI SECRET KEYS 16 | REPLICATE_API_TOKEN= 17 | 18 | #! PRISMA DB URL 19 | DATABASE_URL= 20 | 21 | #! STRIPE ENV KEYS 22 | STRIPE_SECRET_KEY= 23 | STRIPE_WEBHOOK_SECRET= 24 | 25 | #! NEXT HOST PORT URL FOR ENVIRONMENT 26 | NEXT_PUBLIC_HOST=http://localhost:3000 27 | 28 | #! CRISP CHAT SUPPORT SECRET KEYS 29 | NEXT_PUBLIC_CRISP_WEBSITE_ID= 30 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /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 | import { prismadb } from '@/lib/prismadb'; 3 | 4 | const DAY_IN_MS = 84_400_000; 5 | 6 | export const getSubscription = async () => { 7 | const { userId } = auth(); 8 | 9 | if (!userId) return false; 10 | const userSubscription = await prismadb.userSubscription.findUnique({ 11 | where: { 12 | userId, 13 | }, 14 | select: { 15 | stripeSubscriptionId: true, 16 | stripeCurrentPeriodEnd: true, 17 | stripeCustomerId: true, 18 | stripePriceId: true 19 | }, 20 | }); 21 | 22 | if (!userSubscription) return false; 23 | 24 | const isValid = userSubscription.stripePriceId && userSubscription.stripeCurrentPeriodEnd?.getTime()! + DAY_IN_MS > Date.now(); 25 | 26 | return !!isValid; 27 | } -------------------------------------------------------------------------------- /components/Heading.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils'; 2 | import { LucideIcon } from 'lucide-react'; 3 | 4 | interface HeadingProps { 5 | title: string; 6 | description: string; 7 | icon: LucideIcon; 8 | color?: string; 9 | bgColor?: string; 10 | } 11 | 12 | const Heading: React.FC = ({ 13 | title, 14 | description, 15 | icon: Icon, 16 | bgColor, 17 | color, 18 | }) => { 19 | return ( 20 |
21 |
22 | 23 |
24 |
25 |

{title}

26 |

{description}

27 |
28 |
29 | ); 30 | }; 31 | 32 | export default Heading; 33 | -------------------------------------------------------------------------------- /app/(dashboard)/layout.tsx: -------------------------------------------------------------------------------- 1 | import Navbar from '@/components/Navbar'; 2 | import Sidebar from '@/components/Sidebar'; 3 | import { Props } from '@/types'; 4 | 5 | import { getApiLimitCount } from '@/lib/api-limit'; 6 | import { getSubscription } from '@/lib/subscription'; 7 | 8 | const DashboardLayout: React.FC = async ({ children }: Props) => { 9 | const apiLimit = await getApiLimitCount(); 10 | const isPro = await getSubscription(); 11 | 12 | return ( 13 |
14 |
15 | 16 |
17 |
18 | 19 | {children} 20 |
21 |
22 | ); 23 | }; 24 | 25 | export default DashboardLayout; 26 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/eslintrc", 3 | "root": true, 4 | "extends": [ 5 | "next", 6 | "prettier", 7 | "next/core-web-vitals", 8 | "plugin:tailwindcss/recommended" 9 | ], 10 | "plugins": ["tailwindcss"], 11 | "rules": { 12 | "@next/next/no-html-link-for-pages": "off", 13 | "react/jsx-key": "off", 14 | "tailwindcss/no-custom-classname": "off", 15 | "tailwindcss/classnames-order": "error", 16 | "react/no-unescaped-entities": "off", 17 | "@next/next/no-page-custom-font": "off" 18 | }, 19 | "settings": { 20 | "tailwindcss": { 21 | "callees": ["cn"], 22 | "config": "tailwind.config.js" 23 | }, 24 | "next": { 25 | "rootDir": true 26 | } 27 | }, 28 | "overrides": [ 29 | { 30 | "files": ["*.ts", "*.tsx"], 31 | "parser": "@typescript-eslint/parser" 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | generator client { 2 | provider = "prisma-client-js" 3 | } 4 | 5 | datasource db { 6 | provider = "mysql" 7 | url = env("DATABASE_URL") 8 | relationMode = "prisma" 9 | } 10 | 11 | model UserApiLimit { 12 | id String @id @default(cuid()) 13 | userId String @unique 14 | count Int @default(0) 15 | createdAt DateTime @default(now()) 16 | updatedAt DateTime @updatedAt 17 | } 18 | 19 | model UserSubscription { 20 | id String @id @default(cuid()) 21 | userId String @unique 22 | stripeCustomerId String? @unique @map(name: "stripe_customer_id") 23 | stripeSubscriptionId String? @unique @map(name: "stripe_subscription_id") 24 | stripePriceId String? @map(name: "stripe_price_id") 25 | stripeCurrentPeriodEnd DateTime? @map(name: "stripe_current_period_end") 26 | } 27 | -------------------------------------------------------------------------------- /components/ui/progress.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as ProgressPrimitive from "@radix-ui/react-progress" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Progress = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, value, ...props }, ref) => ( 12 | 20 | 24 | 25 | )) 26 | Progress.displayName = ProgressPrimitive.Root.displayName 27 | 28 | export { Progress } 29 | -------------------------------------------------------------------------------- /components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ) 21 | } 22 | ) 23 | Input.displayName = "Input" 24 | 25 | export { Input } 26 | -------------------------------------------------------------------------------- /components/MobileSidebar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState, useEffect } from 'react'; 4 | import { Menu } from 'lucide-react'; 5 | import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'; 6 | import Sidebar from '@/components/Sidebar'; 7 | 8 | type MobileSidebarProps = { 9 | apiLimit: number; 10 | isPro: boolean; 11 | }; 12 | 13 | const MobileSidebar: React.FC = ({ 14 | apiLimit = 0, 15 | isPro = false, 16 | }) => { 17 | const [open, setOpen] = useState(false); 18 | 19 | useEffect(() => { 20 | setOpen(true); 21 | }, []); 22 | 23 | if (!open) return null; 24 | 25 | return ( 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default MobileSidebar; 38 | -------------------------------------------------------------------------------- /constants.ts: -------------------------------------------------------------------------------- 1 | import { Code, ImageIcon, MessageSquare, Music, VideoIcon } from 'lucide-react'; 2 | import { useProModal } from '@/hooks/useProModal'; 3 | 4 | export const MAX_FREE_COUNTS = 15; 5 | 6 | export const tools = [ 7 | { 8 | label: 'Chat', 9 | icon: MessageSquare, 10 | href: '/chat', 11 | color: 'text-violet-500', 12 | bgColor: 'bg-violet-500/10', 13 | }, 14 | { 15 | label: 'Image Generator', 16 | icon: ImageIcon, 17 | color: 'text-orange-700', 18 | bgColor: 'bg-orange-700/10', 19 | }, 20 | { 21 | label: 'Video Generator', 22 | icon: VideoIcon, 23 | color: 'text-prink-700', 24 | bgColor: 'bg-pink-700/10', 25 | }, 26 | { 27 | label: 'Music Generator', 28 | icon: Music, 29 | color: 'text-emerald-500', 30 | bgColor: 'bg-emerald-500/10', 31 | }, 32 | { 33 | label: 'Code Generator', 34 | icon: Code, 35 | color: 'text-blue-700', 36 | bgColor: 'bg-blue-700/10', 37 | }, 38 | ]; 39 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './globals.css'; 2 | import type { Metadata } from 'next'; 3 | import { Inter } from 'next/font/google'; 4 | 5 | import { ClerkProvider } from '@clerk/nextjs'; 6 | 7 | import ModalProvider from '@/components/ModalProvider'; 8 | import ToastProvider from '@/components/ToastProvider'; 9 | import ChatSupportProvider from '@/components/ChatSupportProvider'; 10 | 11 | const inter = Inter({ subsets: ['latin'] }); 12 | 13 | export const metadata: Metadata = { 14 | title: 'D-prompt AI', 15 | description: 'An open source 5-in-1 web based generative AI app.', 16 | }; 17 | 18 | export default function RootLayout({ 19 | children, 20 | }: { 21 | children: React.ReactNode; 22 | }) { 23 | return ( 24 | 25 | 26 | 27 | 28 | 29 | {children} 30 | 31 | 32 | 33 | 34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/image/constants.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 | -------------------------------------------------------------------------------- /.github/workflows/github-actions.yml: -------------------------------------------------------------------------------- 1 | #! Point to my GitHub Actions Deployment (Drakkarrrr) 2 | name: GitHub Actions Deployment 3 | run-name: ${{ github.actor }} Automate CI/CD. 🚀 4 | 5 | on: 6 | push: 7 | branches: ['main'] 8 | pull_request: 9 | branches: ['main'] 10 | 11 | jobs: 12 | Explore-GitHub-Actions: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event." 16 | - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!" 17 | - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}." 18 | - name: Check out repository code 19 | uses: actions/checkout@v3 20 | - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner." 21 | - run: echo "🖥️ The workflow is now ready to test your code on the runner." 22 | - name: List files in the repository 23 | run: | 24 | ls ${{ github.workspace }} 25 | - run: echo "🍏 This job's status is ${{job.status}}." 26 | -------------------------------------------------------------------------------- /components/SubscribeButton.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | import axios from 'axios'; 5 | import { toast } from 'react-hot-toast'; 6 | import { Zap } from 'lucide-react'; 7 | import { Button } from '@/components/ui/button'; 8 | 9 | interface SubscribeButtonProps { 10 | isPro: boolean; 11 | } 12 | 13 | const SubsripeButton: React.FC = ({ isPro = false }) => { 14 | const [loading, setLoading] = useState(false); 15 | 16 | const handleSubscribe = async () => { 17 | try { 18 | setLoading(true); 19 | const response = await axios.get('/api/stripe'); 20 | window.location.href = response.data.url; 21 | } catch (error: any) { 22 | toast.error('Something went wrong'); 23 | } finally { 24 | setLoading(false); 25 | } 26 | }; 27 | 28 | return ( 29 | 37 | ); 38 | }; 39 | 40 | export default SubsripeButton; 41 | -------------------------------------------------------------------------------- /components/LandingNavbar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useAuth } from '@clerk/nextjs'; 4 | import { Montserrat } from 'next/font/google'; 5 | import Image from 'next/image'; 6 | import Link from 'next/link'; 7 | 8 | import { cn } from '@/lib/utils'; 9 | import { Button } from '@/components/ui/button'; 10 | 11 | const font = Montserrat({ 12 | display: 'swap', 13 | weight: '600', 14 | subsets: ['latin'], 15 | }); 16 | 17 | const LandingNavbar = () => { 18 | const { isSignedIn, signOut } = useAuth(); 19 | 20 | return ( 21 | 38 | ); 39 | }; 40 | 41 | export default LandingNavbar; 42 | -------------------------------------------------------------------------------- /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 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/settings/page.tsx: -------------------------------------------------------------------------------- 1 | import Heading from '@/components/Heading'; 2 | import SubscribeButton from '@/components/SubscribeButton'; 3 | import { getSubscription } from '@/lib/subscription'; 4 | import { Settings } from 'lucide-react'; 5 | 6 | const SettingsPage = async () => { 7 | const isPro = await getSubscription(); 8 | 9 | return ( 10 | <> 11 | 18 |
19 |
20 | Gain access to advanced features, no limits, priority support, and 21 | exclusive content. Elevate your creative process to new heights with 22 | premium tools and resources, tailored to those who demand the utmost 23 | from their creative journey. 24 |
25 |
26 | {isPro ? 'Current Plan: Pro Plan' : 'Current Plan: Free Plan'} 27 |
28 | 29 |
30 | 31 | ); 32 | }; 33 | 34 | export default SettingsPage; 35 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/api-limit.ts: -------------------------------------------------------------------------------- 1 | import { auth } from "@clerk/nextjs"; 2 | 3 | import { prismadb } from "@/lib/prismadb"; 4 | import { MAX_FREE_COUNTS } from "@/constants"; 5 | 6 | export const incrementApiLimit = async () => { 7 | const { userId } = auth(); 8 | 9 | if (!userId) return; 10 | 11 | const userApiLimit = await prismadb.userApiLimit.findUnique({ 12 | where: { userId: userId }, 13 | }); 14 | 15 | if (userApiLimit) { 16 | await prismadb.userApiLimit.update({ 17 | where: { userId: userId }, 18 | data: { count: userApiLimit.count + 1 }, 19 | }); 20 | } else { 21 | await prismadb.userApiLimit.create({ 22 | data: { userId: userId, count: 1 }, 23 | }); 24 | } 25 | }; 26 | 27 | export const checkApiLimit = async () => { 28 | const { userId } = auth(); 29 | 30 | if (!userId) return false; 31 | const userApiLimit = await prismadb.userApiLimit.findUnique({ where: { userId: userId } }); 32 | if (!userApiLimit || userApiLimit.count < MAX_FREE_COUNTS) return true; 33 | else return false; 34 | 35 | }; 36 | 37 | export const getApiLimitCount = async () => { 38 | const { userId } = auth(); 39 | 40 | if (!userId) return 0; 41 | 42 | const userApiLimit = await prismadb.userApiLimit.findUnique({ 43 | where: { 44 | userId 45 | } 46 | }); 47 | 48 | if (!userApiLimit) return 0; 49 | 50 | return userApiLimit.count; 51 | }; -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Welcome to D-prompt-AI! We're excited to have you on board. Contributions from the community help improve and enhance the project for everyone. Here's how you can get started: 4 | 5 | ## How to Contribute 6 | 7 | 1. **Fork** the repository on GitHub. 8 | 2. **Clone** the forked repository to your local machine. 9 | 3. Make your changes or additions in your local repository. 10 | 4. Test your changes to ensure they work as expected. 11 | 5. **Commit** your changes with descriptive commit messages. 12 | 6. **Push** your changes to your forked repository on GitHub. 13 | 7. Create a new **Pull Request** from your forked repository to the main project repository. 14 | 15 | ## Code of Conduct 16 | 17 | Please note that we have a [Code of Conduct](CODE_OF_CONDUCT.md). We expect all contributors to adhere to it to maintain a positive and respectful community. 18 | 19 | ## Report Issues 20 | 21 | If you find any issues or have suggestions for improvements, you can open an [issue](https://github.com/drakkarrr/d-prompt-ai/issues) on GitHub. Be sure to provide as much detail as possible to help us understand and address the problem. 22 | 23 | ## Get in Touch 24 | 25 | If you have questions or need help with the contribution process, feel free to reach out on our [community chat](https://gitter.im/yourproject/community) or by [email](mailto:drakkar.alpha00@gmail.com). 26 | 27 | We appreciate your interest in contributing to [Your Project Name]. Together, we can make this project even better! 28 | -------------------------------------------------------------------------------- /app/api/video/route.ts: -------------------------------------------------------------------------------- 1 | import Replicate from "replicate"; 2 | import { auth } from "@clerk/nextjs"; 3 | import { NextResponse } from "next/server"; 4 | import { incrementApiLimit, checkApiLimit } from "@/lib/api-limit"; 5 | import { getSubscription } from "@/lib/subscription"; 6 | 7 | const replicate = new Replicate({ 8 | auth: process.env.REPLICATE_API_TOKEN!, 9 | }); 10 | 11 | export async function POST( 12 | req: Request 13 | ) { 14 | try { 15 | const { userId } = auth(); 16 | const body = await req.json(); 17 | const { prompt } = body; 18 | 19 | if (!userId) return new NextResponse("Unauthorized", { status: 401 }); 20 | if (!prompt) return new NextResponse("Prompt is required", { status: 400 }); 21 | 22 | const freeTier = await checkApiLimit(); 23 | const isPro = await getSubscription() 24 | 25 | if (!freeTier && !isPro) return new NextResponse('Free tier limit reached', { status: 403 }); 26 | 27 | const response = await replicate.run( 28 | "anotherjesse/zeroscope-v2-xl:9f747673945c62801b13b84701c783929c0ee784e4748ec062204894dda1a351", 29 | { 30 | input: { 31 | prompt 32 | } 33 | } 34 | ); 35 | 36 | if (!isPro) { 37 | await incrementApiLimit(); 38 | } 39 | 40 | return NextResponse.json(response); 41 | } catch (error) { 42 | console.log('[VIDEO ERROR:]', error); 43 | return new NextResponse("Internal Error", { status: 500 }); 44 | } 45 | }; -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { User } from "@prisma/client" 2 | import type { Icon } from "lucide-react" 3 | 4 | import { Icons } from "@/components/icons" 5 | 6 | export type Props = { 7 | children: React.ReactNode 8 | } 9 | 10 | export type NavItem = { 11 | title: string 12 | href: string 13 | disabled?: boolean 14 | } 15 | 16 | export type MainNavItem = NavItem 17 | 18 | export type SidebarNavItem = { 19 | title: string 20 | disabled?: boolean 21 | external?: boolean 22 | icon?: keyof typeof Icons 23 | } & ( 24 | | { 25 | href: string 26 | items?: never 27 | } 28 | | { 29 | href?: string 30 | items: NavLink[] 31 | } 32 | ) 33 | 34 | export type SiteConfig = { 35 | name: string 36 | description: string 37 | url: string 38 | ogImage: string 39 | links: { 40 | twitter: string 41 | github: string 42 | } 43 | } 44 | 45 | export type DocsConfig = { 46 | mainNav: MainNavItem[] 47 | sidebarNav: SidebarNavItem[] 48 | } 49 | 50 | export type MarketingConfig = { 51 | mainNav: MainNavItem[] 52 | } 53 | 54 | export type DashboardConfig = { 55 | mainNav: MainNavItem[] 56 | sidebarNav: SidebarNavItem[] 57 | } 58 | 59 | export type SubscriptionPlan = { 60 | name: string 61 | description: string 62 | stripePriceId: string 63 | } 64 | 65 | export type UserSubscriptionPlan = SubscriptionPlan & 66 | Pick & { 67 | stripeCurrentPeriodEnd: number 68 | isPro: boolean 69 | } -------------------------------------------------------------------------------- /app/api/music/route.ts: -------------------------------------------------------------------------------- 1 | import Replicate from "replicate"; 2 | import { auth } from "@clerk/nextjs"; 3 | import { NextResponse } from "next/server"; 4 | import { incrementApiLimit, checkApiLimit } from "@/lib/api-limit"; 5 | import { getSubscription } from "@/lib/subscription"; 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) return new NextResponse("Unauthorized", { status: 401 }); 21 | if (!prompt) return new NextResponse("Prompt is required", { status: 400 }); 22 | 23 | const freeTier = await checkApiLimit(); 24 | const isPro = await getSubscription() 25 | 26 | if (!freeTier && !isPro) return new NextResponse('Free tier limit reached', { status: 403 }); 27 | 28 | const response = await replicate.run( 29 | "riffusion/riffusion:8cf61ea6c56afd61d8f5b9ffd14d7c216c0a93844ce2d82ac1c9ecc9c7f24e05", 30 | { 31 | input: { 32 | prompt_a: prompt 33 | } 34 | } 35 | ); 36 | 37 | if (!isPro) { 38 | await incrementApiLimit(); 39 | } 40 | 41 | return NextResponse.json(response); 42 | } catch (error) { 43 | console.log('[MUSIC_ERROR]', error); 44 | return new NextResponse("Internal Error", { status: 500 }); 45 | } 46 | }; -------------------------------------------------------------------------------- /components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import * as React from 'react'; 3 | import * as AvatarPrimitive from '@radix-ui/react-avatar'; 4 | import { cn } from '@/lib/utils'; 5 | 6 | const Avatar = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >(({ className, ...props }, ref) => ( 10 | 18 | )); 19 | Avatar.displayName = AvatarPrimitive.Root.displayName; 20 | 21 | const AvatarImage = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef 24 | >(({ className, ...props }, ref) => ( 25 | 30 | )); 31 | AvatarImage.displayName = AvatarPrimitive.Image.displayName; 32 | 33 | const AvatarFallback = React.forwardRef< 34 | React.ElementRef, 35 | React.ComponentPropsWithoutRef 36 | >(({ className, ...props }, ref) => ( 37 | 45 | )); 46 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; 47 | export { Avatar, AvatarImage, AvatarFallback }; 48 | -------------------------------------------------------------------------------- /app/api/chat/route.ts: -------------------------------------------------------------------------------- 1 | import { auth } from '@clerk/nextjs'; 2 | import { NextResponse } from 'next/server'; 3 | import { Configuration, OpenAIApi } from 'openai' 4 | 5 | import { checkApiLimit, incrementApiLimit } from '@/lib/api-limit'; 6 | import { getSubscription } from '@/lib/subscription'; 7 | 8 | const configuration = new Configuration({ 9 | apiKey: process.env.OPENAI_API_KEY, 10 | }) 11 | 12 | const openai = new OpenAIApi(configuration) 13 | 14 | 15 | export const POST = async (req: Request) => { 16 | console.log(req.body); 17 | 18 | try { 19 | const { userId } = auth() 20 | const body = await req.json() 21 | const { messages } = body 22 | 23 | if (!userId) return new NextResponse('Unauthorized', { status: 401 }); 24 | if (!configuration.apiKey) return new NextResponse('Open AI API Keys not configured', { status: 500 }); 25 | if (!messages) return new NextResponse('No messages found', { status: 400 }); 26 | 27 | const freeTier = await checkApiLimit(); 28 | const isPro = await getSubscription() 29 | 30 | if (!freeTier && !isPro) return new NextResponse('Free tier limit reached', { status: 403 }); 31 | 32 | const response = await openai.createChatCompletion({ 33 | model: 'gpt-3.5-turbo', 34 | messages 35 | }) 36 | 37 | if (!isPro) { 38 | await incrementApiLimit(); 39 | } 40 | 41 | return NextResponse.json(response.data.choices[0].message); 42 | 43 | 44 | } catch (err) { 45 | console.log(`Conversation error: ${err}`); 46 | return new NextResponse(`Internal error: ${err}`, { status: 500 }); 47 | } 48 | } -------------------------------------------------------------------------------- /components/FreeCounter.tsx: -------------------------------------------------------------------------------- 1 | import { Zap } from 'lucide-react'; 2 | import { useEffect, useState } from 'react'; 3 | 4 | import { MAX_FREE_COUNTS } from '@/constants'; 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/useProModal'; 9 | 10 | interface FreeCounterProps { 11 | isPro: boolean; 12 | apiLimit: number; 13 | } 14 | 15 | const FreeCounter: React.FC = ({ 16 | isPro = false, 17 | apiLimit = 0, 18 | }) => { 19 | const [mounted, setMounted] = useState(false); 20 | const proModal = useProModal(); 21 | 22 | useEffect(() => { 23 | setMounted(true); 24 | }, []); 25 | 26 | if (!mounted) return null; 27 | if (isPro) return null; 28 | 29 | return ( 30 |
31 | 32 | 33 |
34 |

35 | {apiLimit} / {MAX_FREE_COUNTS} Free Generations 36 |

37 | 41 |
42 | 50 |
51 |
52 |
53 | ); 54 | }; 55 | 56 | export default FreeCounter; 57 | -------------------------------------------------------------------------------- /app/api/image/route.ts: -------------------------------------------------------------------------------- 1 | import { auth } from '@clerk/nextjs'; 2 | import { NextResponse } from 'next/server'; 3 | import { Configuration, OpenAIApi } from 'openai' 4 | import { checkApiLimit, incrementApiLimit } from '@/lib/api-limit'; 5 | import { getSubscription } from '@/lib/subscription'; 6 | 7 | const configuration = new Configuration({ 8 | apiKey: process.env.OPENAI_API_KEY, 9 | }) 10 | 11 | const openai = new OpenAIApi(configuration) 12 | 13 | export const POST = async (req: Request) => { 14 | console.log(req.body); 15 | 16 | try { 17 | const { userId } = auth() 18 | const body = await req.json() 19 | const { prompt, amount = 1, resolution = '512x512' } = body 20 | 21 | if (!userId) return new NextResponse('Unauthorized', { status: 401 }); 22 | if (!configuration.apiKey) return new NextResponse('Open AI API Keys not configured', { status: 500 }); 23 | 24 | if (!prompt) return new NextResponse('No prompt found', { status: 400 }); 25 | if (!amount) return new NextResponse('No amount found', { status: 400 }); 26 | if (!resolution) return new NextResponse('No resolution found', { status: 400 }); 27 | 28 | const freeTier = await checkApiLimit(); 29 | const isPro = await getSubscription() 30 | 31 | if (!freeTier && !isPro) return new NextResponse('Free tier limit reached', { status: 403 }); 32 | 33 | const response = await openai.createImage({ 34 | prompt, 35 | n: parseInt(amount, 10), 36 | size: resolution, 37 | }) 38 | 39 | if (!isPro) { 40 | await incrementApiLimit(); 41 | } 42 | 43 | return NextResponse.json(response.data.data); 44 | 45 | 46 | } catch (err) { 47 | console.log(`Image Generator error: ${err}`); 48 | return new NextResponse(`Internal error: ${err}`, { status: 500 }); 49 | } 50 | } -------------------------------------------------------------------------------- /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 | import { checkApiLimit, incrementApiLimit } from '@/lib/api-limit'; 5 | import { getSubscription } from '@/lib/subscription'; 6 | 7 | const configuration = new Configuration({ 8 | apiKey: process.env.OPENAI_API_KEY, 9 | }) 10 | 11 | const openai = new OpenAIApi(configuration) 12 | 13 | const instructionMessage: ChatCompletionRequestMessage = { 14 | role: 'system', 15 | content: "You are a clever code generator. You must answer only in markdown code snippets. Use code comments for explanations." 16 | } 17 | 18 | export const POST = async (req: Request) => { 19 | console.log(req.body); 20 | 21 | try { 22 | const { userId } = auth() 23 | const body = await req.json() 24 | const { messages } = body 25 | 26 | if (!userId) return new NextResponse('Unauthorized', { status: 401 }); 27 | if (!configuration.apiKey) return new NextResponse('Open AI API Keys not configured', { status: 500 }); 28 | if (!messages) return new NextResponse('No messages found', { status: 400 }); 29 | 30 | const freeTier = await checkApiLimit(); 31 | const isPro = await getSubscription() 32 | 33 | if (!freeTier && !isPro) return new NextResponse('Free tier limit reached', { status: 403 }); 34 | 35 | const response = await openai.createChatCompletion({ 36 | model: 'gpt-3.5-turbo', 37 | messages: [instructionMessage, ...messages], 38 | }) 39 | 40 | if (!isPro) { 41 | await incrementApiLimit(); 42 | } 43 | 44 | return NextResponse.json(response.data.choices[0].message); 45 | 46 | 47 | } catch (err) { 48 | console.log(`Code error: ${err}`); 49 | return new NextResponse(`Code error: ${err}`, { status: 500 }); 50 | } 51 | } -------------------------------------------------------------------------------- /public/technical-support.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 11 | 14 | 21 | 22 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | 10 | --muted: 210 40% 96.1%; 11 | --muted-foreground: 215.4 16.3% 46.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | 16 | --card: 0 0% 100%; 17 | --card-foreground: 222.2 84% 4.9%; 18 | 19 | --border: 214.3 31.8% 91.4%; 20 | --input: 214.3 31.8% 91.4%; 21 | 22 | --primary: 222.2 47.4% 11.2%; 23 | --primary-foreground: 210 40% 98%; 24 | 25 | --secondary: 210 40% 96.1%; 26 | --secondary-foreground: 222.2 47.4% 11.2%; 27 | 28 | --accent: 210 40% 96.1%; 29 | --accent-foreground: 222.2 47.4% 11.2%; 30 | 31 | --destructive: 0 84.2% 60.2%; 32 | --destructive-foreground: 210 40% 98%; 33 | 34 | --ring: 215 20.2% 65.1%; 35 | 36 | --radius: 0.5rem; 37 | } 38 | 39 | .dark { 40 | --background: 222.2 84% 4.9%; 41 | --foreground: 210 40% 98%; 42 | 43 | --muted: 217.2 32.6% 17.5%; 44 | --muted-foreground: 215 20.2% 65.1%; 45 | 46 | --popover: 222.2 84% 4.9%; 47 | --popover-foreground: 210 40% 98%; 48 | 49 | --card: 222.2 84% 4.9%; 50 | --card-foreground: 210 40% 98%; 51 | 52 | --border: 217.2 32.6% 17.5%; 53 | --input: 217.2 32.6% 17.5%; 54 | 55 | --primary: 210 40% 98%; 56 | --primary-foreground: 222.2 47.4% 11.2%; 57 | 58 | --secondary: 217.2 32.6% 17.5%; 59 | --secondary-foreground: 210 40% 98%; 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: 0 85.7% 97.3%; 66 | 67 | --ring: 217.2 32.6% 17.5%; 68 | } 69 | } 70 | 71 | html, 72 | body, 73 | :root { 74 | height: 100%; 75 | } 76 | 77 | @layer base { 78 | * { 79 | @apply border-border; 80 | } 81 | body { 82 | @apply bg-background text-foreground; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /components/LandingHero.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import Link from 'next/link'; 4 | import TypewriterComponent from 'typewriter-effect'; 5 | import { useAuth } from '@clerk/nextjs'; 6 | import { Button } from '@/components/ui/button'; 7 | 8 | const LandingHero = () => { 9 | const { isSignedIn } = useAuth(); 10 | 11 | return ( 12 |
13 |
14 |

D-prompt AI Generative for

15 |
16 | 29 |
30 |
31 |
32 | A Generative AI app that embarks on creative discovery and immerse 33 | yourself in dynamic conversations, captivating visuals, harmonious 34 | melodies, assistive code generation and more. 35 |

No credit card required! 36 |
37 |
38 | 39 | 45 | 46 |
47 |
48 | Developed by   49 | 53 | Drakkar 54 | 55 |
56 |
57 | ); 58 | }; 59 | 60 | export default LandingHero; 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 | danger: 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: 22 | 'bg-gradient-to-r from-[#61cfeb] via-[#9281d8d5] to-[#9076fc] text-white border-0', 23 | }, 24 | size: { 25 | default: 'h-10 px-4 py-2', 26 | sm: 'h-9 rounded-md px-3', 27 | lg: 'h-11 rounded-md px-8', 28 | icon: 'h-10 w-10', 29 | }, 30 | }, 31 | defaultVariants: { 32 | variant: 'default', 33 | size: 'default', 34 | }, 35 | } 36 | ); 37 | 38 | export interface ButtonProps 39 | extends React.ButtonHTMLAttributes, 40 | VariantProps { 41 | asChild?: boolean; 42 | } 43 | 44 | const Button = React.forwardRef( 45 | ({ className, variant, size, asChild = false, ...props }, ref) => { 46 | const Comp = asChild ? Slot : 'button'; 47 | return ( 48 | 53 | ); 54 | } 55 | ); 56 | Button.displayName = 'Button'; 57 | 58 | export { Button, buttonVariants }; 59 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/api/webhook/route.ts: -------------------------------------------------------------------------------- 1 | import Stripe from "stripe" 2 | import { headers } from "next/headers" 3 | import { NextResponse } from "next/server" 4 | import { prismadb } from "@/lib/prismadb" 5 | import { stripe } from "@/lib/stripe-connect" 6 | 7 | export const POST = async (req: Request) => { 8 | const body = await req.text() 9 | const signature = headers().get("Stripe-Signature") as string 10 | 11 | let event: Stripe.Event 12 | 13 | try { 14 | event = stripe.webhooks.constructEvent( 15 | body, 16 | signature, 17 | process.env.STRIPE_WEBHOOK_SECRET! 18 | ) 19 | } catch (error: any) { 20 | return new NextResponse(`Webhook Error: ${error.message}`, { status: 400 }) 21 | } 22 | 23 | const session = event.data.object as Stripe.Checkout.Session 24 | 25 | if (event.type === "checkout.session.completed") { 26 | const subscription = await stripe.subscriptions.retrieve( 27 | session.subscription as string 28 | ) 29 | 30 | if (!session?.metadata?.userId) return new NextResponse("User id is required", { status: 400 }); 31 | 32 | 33 | await prismadb.userSubscription.create({ 34 | data: { 35 | userId: session?.metadata?.userId, 36 | stripeSubscriptionId: subscription.id, 37 | stripeCustomerId: subscription.customer as string, 38 | stripePriceId: subscription.items.data[0].price.id, 39 | stripeCurrentPeriodEnd: new Date(subscription.current_period_end * 1000), 40 | }, 41 | }) 42 | } 43 | 44 | if (event.type === "invoice.payment_succeeded") { 45 | const subscription = await stripe.subscriptions.retrieve(session.subscription as string) 46 | 47 | await prismadb.userSubscription.update({ 48 | where: { stripeSubscriptionId: subscription.id }, 49 | data: { 50 | stripePriceId: subscription.items.data[0].price.id, 51 | stripeCurrentPeriodEnd: new Date( 52 | subscription.current_period_end * 1000 53 | ), 54 | }, 55 | }) 56 | } 57 | 58 | return new NextResponse(null, { status: 200 }) 59 | }; -------------------------------------------------------------------------------- /components/LandingPageContents.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; 4 | 5 | const testimonials = [ 6 | { 7 | id: 1, 8 | name: 'John Doe', 9 | title: 'Teacher', 10 | org: 'Google', 11 | desc: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.', 12 | }, 13 | { 14 | id: 2, 15 | name: 'Jane Doe', 16 | title: 'CEO', 17 | org: 'Facebook', 18 | desc: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.', 19 | }, 20 | { 21 | id: 3, 22 | name: 'Drak Doe', 23 | title: 'CEO', 24 | org: 'Google', 25 | desc: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.', 26 | }, 27 | { 28 | id: 4, 29 | name: 'Chris Doe', 30 | title: 'Sofware Developer', 31 | org: 'Google', 32 | desc: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Quisquam, quos.', 33 | }, 34 | ]; 35 | 36 | const LandingPageContents = () => { 37 | return ( 38 | <> 39 |
40 |

41 | Testimonials 42 |

43 |
44 | {testimonials.map((testimonial) => ( 45 | 49 | 50 | 51 | {testimonial.desc} 52 | 53 | 54 |
55 |

{testimonial.name}

56 |

{testimonial.title}

57 |
58 |
59 |
60 |
61 | ))} 62 |
63 |
64 |
65 | Developed by Drakkar 66 |
67 | 68 | ); 69 | }; 70 | 71 | export default LandingPageContents; 72 | -------------------------------------------------------------------------------- /app/api/stripe/route.ts: -------------------------------------------------------------------------------- 1 | import { auth, currentUser } from "@clerk/nextjs"; 2 | import { NextResponse } from "next/server"; 3 | import { stripe } from "@/lib/stripe-connect"; 4 | import { prismadb } from "@/lib/prismadb"; 5 | import { absoluteUrl } from "@/lib/utils"; 6 | 7 | 8 | const settingsUrl = absoluteUrl("/settings"); 9 | export const GET = async () => { 10 | try { 11 | const { userId } = auth(); 12 | const user = await currentUser(); 13 | 14 | if (!userId || !user) return new NextResponse("Unauthorized", { status: 401 }); 15 | 16 | const userSubscription = await prismadb.userSubscription.findUnique({ 17 | where: { userId } 18 | }) 19 | 20 | if (userSubscription && userSubscription.stripeCustomerId) { 21 | const stripeSession = await stripe.billingPortal.sessions.create({ 22 | customer: userSubscription.stripeCustomerId, 23 | return_url: settingsUrl, 24 | }) 25 | 26 | return new NextResponse(JSON.stringify({ url: stripeSession.url })) 27 | } 28 | 29 | const stripeSession = await stripe.checkout.sessions.create({ 30 | success_url: settingsUrl, 31 | cancel_url: settingsUrl, 32 | payment_method_types: ["card"], 33 | mode: "subscription", 34 | billing_address_collection: "auto", 35 | customer_email: user.emailAddresses[0].emailAddress, 36 | line_items: [ 37 | { 38 | price_data: { 39 | currency: "USD", 40 | product_data: { 41 | name: "D-Prompt Pro", 42 | description: "Unlimited AI Generations" 43 | }, 44 | unit_amount: 2000, 45 | recurring: { 46 | interval: "month" 47 | } 48 | }, 49 | quantity: 1, 50 | }, 51 | ], 52 | metadata: { 53 | userId, 54 | }, 55 | }) 56 | 57 | return new NextResponse(JSON.stringify({ url: stripeSession.url })) 58 | } catch (error) { 59 | console.log("[STRIPE_ERROR]", error); 60 | return new NextResponse("Internal Error", { status: 500 }); 61 | } 62 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d-prompt-ai", 3 | "version": "0.1.0", 4 | "private": true, 5 | "main": "index.js", 6 | "repository": "https://github.com/Drakkarrr/D-prompt-AI.git", 7 | "author": "Drakkarrr ", 8 | "license": "MIT", 9 | "scripts": { 10 | "dev": "next dev", 11 | "build": "next build", 12 | "start": "next start", 13 | "lint": "next lint --fix", 14 | "precommit": "lint-staged", 15 | "test": "yarn lint", 16 | "prepare": "husky install", 17 | "postinstall": "prisma generate" 18 | }, 19 | "husky": { 20 | "hooks": { 21 | "pre-commit": "lint-staged" 22 | } 23 | }, 24 | "lint-staged": { 25 | "*.tsx": "eslint --fix", 26 | "*.ts": "stylelint --fix" 27 | }, 28 | "dependencies": { 29 | "@clerk/nextjs": "^4.29.2", 30 | "@hookform/resolvers": "^3.1.1", 31 | "@prisma/client": "^5.0.0", 32 | "@radix-ui/react-avatar": "^1.0.3", 33 | "@radix-ui/react-dialog": "^1.0.4", 34 | "@radix-ui/react-label": "^2.0.2", 35 | "@radix-ui/react-progress": "^1.0.3", 36 | "@radix-ui/react-select": "^1.2.2", 37 | "@radix-ui/react-slot": "^1.0.2", 38 | "@types/node": "20.4.2", 39 | "@types/react": "18.2.15", 40 | "@types/react-dom": "18.2.7", 41 | "add": "^2.0.6", 42 | "autoprefixer": "10.4.14", 43 | "axios": "^1.4.0", 44 | "button": "^1.1.1", 45 | "class-variance-authority": "^0.7.0", 46 | "clsx": "^2.0.0", 47 | "crisp-sdk-web": "^1.0.21", 48 | "eslint": "8.45.0", 49 | "eslint-config-next": "^13.4.10", 50 | "init": "^0.1.2", 51 | "lucide-react": "^0.262.0", 52 | "next": "13.4.10", 53 | "openai": "^3.3.0", 54 | "postcss": "8.4.26", 55 | "react": "18.2.0", 56 | "react-dom": "18.2.0", 57 | "react-hook-form": "^7.45.2", 58 | "react-hot-toast": "^2.4.1", 59 | "react-markdown": "^8.0.7", 60 | "replicate": "^0.14.0", 61 | "shadcn-ui": "^0.3.0", 62 | "stripe": "^12.16.0", 63 | "tailwind-merge": "^1.14.0", 64 | "tailwindcss": "3.3.3", 65 | "tailwindcss-animate": "^1.0.6", 66 | "typescript": "5.1.6", 67 | "typewriter-effect": "^2.20.1", 68 | "zod": "^3.21.4", 69 | "zustand": "^4.3.9" 70 | }, 71 | "devDependencies": { 72 | "eslint-config-prettier": "^8.8.0", 73 | "eslint-plugin-tailwindcss": "^3.13.0", 74 | "husky": "^8.0.0", 75 | "prettier": "^3.0.0", 76 | "prettier-plugin-tailwindcss": "^0.4.1", 77 | "prisma": "^5.0.0" 78 | } 79 | } 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 | '3xl': '1600px', 17 | '4xl': '1800px', 18 | }, 19 | }, 20 | extend: { 21 | colors: { 22 | border: 'hsl(var(--border))', 23 | input: 'hsl(var(--input))', 24 | ring: 'hsl(var(--ring))', 25 | background: 'hsl(var(--background))', 26 | foreground: 'hsl(var(--foreground))', 27 | primary: { 28 | DEFAULT: 'hsl(var(--primary))', 29 | foreground: 'hsl(var(--primary-foreground))', 30 | }, 31 | secondary: { 32 | DEFAULT: 'hsl(var(--secondary))', 33 | foreground: 'hsl(var(--secondary-foreground))', 34 | }, 35 | destructive: { 36 | DEFAULT: 'hsl(var(--destructive))', 37 | foreground: 'hsl(var(--destructive-foreground))', 38 | }, 39 | muted: { 40 | DEFAULT: 'hsl(var(--muted))', 41 | foreground: 'hsl(var(--muted-foreground))', 42 | }, 43 | accent: { 44 | DEFAULT: 'hsl(var(--accent))', 45 | foreground: 'hsl(var(--accent-foreground))', 46 | }, 47 | popover: { 48 | DEFAULT: 'hsl(var(--popover))', 49 | foreground: 'hsl(var(--popover-foreground))', 50 | }, 51 | card: { 52 | DEFAULT: 'hsl(var(--card))', 53 | foreground: 'hsl(var(--card-foreground))', 54 | }, 55 | }, 56 | borderRadius: { 57 | lg: 'var(--radius)', 58 | md: 'calc(var(--radius) - 2px)', 59 | sm: 'calc(var(--radius) - 4px)', 60 | }, 61 | keyframes: { 62 | 'accordion-down': { 63 | from: { height: 0 }, 64 | to: { height: 'var(--radix-accordion-content-height)' }, 65 | }, 66 | 'accordion-up': { 67 | from: { height: 'var(--radix-accordion-content-height)' }, 68 | to: { height: 0 }, 69 | }, 70 | }, 71 | animation: { 72 | 'accordion-down': 'accordion-down 0.2s ease-out', 73 | 'accordion-up': 'accordion-up 0.2s ease-out', 74 | }, 75 | }, 76 | }, 77 | plugins: [require('tailwindcss-animate')], 78 | }; 79 | -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useRouter as Router } from 'next/navigation'; 4 | 5 | import { Card } from '@/components/ui/card'; 6 | import { cn } from '@/lib/utils'; 7 | 8 | import { 9 | ArrowRight, 10 | CodeIcon, 11 | ImageIcon, 12 | MessageSquare, 13 | MusicIcon, 14 | VideoIcon, 15 | } from 'lucide-react'; 16 | 17 | const tools = [ 18 | { 19 | label: 'Chat Generator', 20 | icon: MessageSquare, 21 | href: '/chat', 22 | color: 'text-violet-500', 23 | bgColor: 'bg-blue-500/10', 24 | }, 25 | { 26 | label: 'Image Generator', 27 | icon: ImageIcon, 28 | href: '/image', 29 | color: 'text-orange-700', 30 | bgColor: 'bg-orange-500/10', 31 | }, 32 | { 33 | label: 'Video Generator', 34 | icon: VideoIcon, 35 | href: '/video', 36 | color: 'text-pink-300', 37 | bgColor: 'bg-pink-500/10', 38 | }, 39 | { 40 | label: 'Music Generator', 41 | icon: MusicIcon, 42 | href: '/music', 43 | color: 'text-emerald-500', 44 | bgColor: 'bg-emerald-500/10', 45 | }, 46 | { 47 | label: 'Code Generator', 48 | icon: CodeIcon, 49 | href: '/code', 50 | color: 'text-blue-700', 51 | bgColor: 'bg-blue-500/10', 52 | }, 53 | ]; 54 | 55 | const DashboardPage = () => { 56 | const router = Router(); 57 | 58 | return ( 59 | <> 60 |
61 |

62 | Get started with D-propmt AI 63 |

64 |

65 | Generate contents using OpenAI GPT3.5 and Replicate API 66 |

67 |
68 |
69 | {tools.map((tool) => ( 70 | router.push(tool.href)} 72 | key={tool.href} 73 | className='flex cursor-pointer items-center justify-between border-black/5 p-4 transition hover:shadow-md' 74 | > 75 |
76 |
77 | 78 |
79 |
{tool.label}
80 |
81 | 82 |
83 | ))} 84 |
85 | 86 | ); 87 | }; 88 | 89 | export default DashboardPage; 90 | -------------------------------------------------------------------------------- /components/ProModal.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import axios from 'axios'; 4 | import { useState } from 'react'; 5 | import { Check, Zap } from 'lucide-react'; 6 | import { toast } from 'react-hot-toast'; 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/useProModal'; 19 | import { tools } from '@/constants'; 20 | import { Card } from '@/components/ui/card'; 21 | import { cn } from '@/lib/utils'; 22 | 23 | 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 | 42 | 43 | 44 | 45 |
46 | Upgrade to D-prompt 47 | 48 | pro 49 | 50 |
51 |
52 | 53 | {tools.map((tool) => ( 54 | 58 |
59 |
60 | 61 |
62 |
{tool.label}
63 |
64 | 65 |
66 | ))} 67 |
68 |
69 | 70 | 80 | 81 |
82 |
83 | ); 84 | }; 85 | 86 | export default ProModal; 87 | -------------------------------------------------------------------------------- /components/Sidebar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import Image from 'next/image'; 4 | import Link from 'next/link'; 5 | import { usePathname } from 'next/navigation'; 6 | 7 | import { 8 | CodeIcon, 9 | ImageIcon, 10 | LayoutDashboard, 11 | MessageSquare, 12 | MusicIcon, 13 | Settings, 14 | VideoIcon, 15 | } from 'lucide-react'; 16 | 17 | import { Montserrat } from 'next/font/google'; 18 | import { cn } from '@/lib/utils'; 19 | import FreeCounter from '@/components/FreeCounter'; 20 | 21 | const montserrat = Montserrat({ 22 | weight: '700', 23 | subsets: ['latin'], 24 | }); 25 | 26 | const routes = [ 27 | { 28 | label: 'Dashboard', 29 | icon: LayoutDashboard, 30 | href: '/dashboard', 31 | color: 'text-sky-500', 32 | }, 33 | { 34 | label: 'Chat Generator', 35 | icon: MessageSquare, 36 | href: '/chat', 37 | color: 'text-violet-500', 38 | }, 39 | { 40 | label: 'Image Generator', 41 | icon: ImageIcon, 42 | href: '/image', 43 | color: 'text-orange-700', 44 | }, 45 | { 46 | label: 'Video Generator', 47 | icon: VideoIcon, 48 | href: '/video', 49 | color: 'text-pink-300', 50 | }, 51 | { 52 | label: 'Music Generator', 53 | icon: MusicIcon, 54 | href: '/music', 55 | color: 'text-emerald-500', 56 | }, 57 | { 58 | label: 'Code Generator', 59 | icon: CodeIcon, 60 | href: '/code', 61 | color: 'text-blue-700', 62 | }, 63 | { 64 | label: 'Settings', 65 | icon: Settings, 66 | href: '/settings', 67 | }, 68 | ]; 69 | 70 | interface SidebarProps { 71 | apiLimit: number; 72 | isPro: boolean; 73 | } 74 | 75 | const Sidebar: React.FC = ({ apiLimit = 0, isPro = false }) => { 76 | const pathname = usePathname(); 77 | 78 | return ( 79 |
80 |
81 | 82 |
83 | Logo 84 |
85 |

86 | D-prompt 87 |

88 | 89 |
90 | {routes.map((route) => ( 91 | 99 |
100 | 101 | {route.label} 102 |
103 | 104 | ))} 105 |
106 |
107 | 108 |
109 | ); 110 | }; 111 | 112 | export default Sidebar; 113 | -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/music/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | import { useRouter as Router } from 'next/navigation'; 5 | import axios from 'axios'; 6 | 7 | import Heading from '@/components/Heading'; 8 | import { Form, FormControl, FormField, FormItem } from '@/components/ui/form'; 9 | import { Input } from '@/components/ui/input'; 10 | import { Button } from '@/components/ui/button'; 11 | import Empty from '@/components/Empty'; 12 | import Loader from '@/components/Loader'; 13 | 14 | import { useForm } from 'react-hook-form'; 15 | import { z } from 'zod'; 16 | import { zodResolver } from '@hookform/resolvers/zod'; 17 | import { useProModal } from '@/hooks/useProModal'; 18 | 19 | import { Music } from 'lucide-react'; 20 | import { formSchema } from './constants'; 21 | import toast from 'react-hot-toast'; 22 | 23 | const MusicGeneratorPage = () => { 24 | const proModal = useProModal(); 25 | const router = Router(); 26 | const [music, setMusic] = useState(); 27 | 28 | const form = useForm>({ 29 | resolver: zodResolver(formSchema), 30 | defaultValues: { 31 | prompt: '', 32 | }, 33 | }); 34 | 35 | const isLoading = form.formState.isSubmitting; 36 | 37 | const onSubmit = async (values: z.infer) => { 38 | try { 39 | setMusic(undefined); 40 | 41 | const response = await axios.post('/api/music', values); 42 | // console.log(response); 43 | 44 | setMusic(response.data.audio); 45 | form.reset(); 46 | } catch (error: any) { 47 | if (error?.response?.status === 403) proModal.onOpen(); 48 | else toast.error('Something went wrong!'); 49 | } finally { 50 | router.refresh(); 51 | } 52 | }; 53 | 54 | return ( 55 |
56 | 63 |
64 |
65 | 69 | ( 72 | 73 | 74 | 80 | 81 | 82 | )} 83 | /> 84 | 92 | 93 | 94 | {isLoading && ( 95 |
96 | 97 |
98 | )} 99 | {!music && !isLoading && } 100 | {music && ( 101 | 104 | )} 105 |
106 |
107 | ); 108 | }; 109 | 110 | export default MusicGeneratorPage; 111 | -------------------------------------------------------------------------------- /app/(dashboard)/(routes)/video/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | import { useRouter as Router } from 'next/navigation'; 5 | import axios from 'axios'; 6 | import toast from 'react-hot-toast'; 7 | 8 | import Heading from '@/components/Heading'; 9 | import { Form, FormControl, FormField, FormItem } from '@/components/ui/form'; 10 | import { Input } from '@/components/ui/input'; 11 | import { Button } from '@/components/ui/button'; 12 | import Empty from '@/components/Empty'; 13 | import Loader from '@/components/Loader'; 14 | 15 | import { useForm } from 'react-hook-form'; 16 | import { useProModal } from '@/hooks/useProModal'; 17 | import { z } from 'zod'; 18 | import { zodResolver } from '@hookform/resolvers/zod'; 19 | 20 | import { Video } from 'lucide-react'; 21 | import { formSchema } from './constants'; 22 | 23 | const VideoGeneratorPage = () => { 24 | const proModal = useProModal(); 25 | const router = Router(); 26 | const [video, setVideo] = useState(); 27 | 28 | const form = useForm>({ 29 | resolver: zodResolver(formSchema), 30 | defaultValues: { 31 | prompt: '', 32 | }, 33 | }); 34 | 35 | const isLoading = form.formState.isSubmitting; 36 | 37 | const onSubmit = async (values: z.infer) => { 38 | try { 39 | setVideo(undefined); 40 | 41 | const response = await axios.post('/api/video', values); 42 | // console.log(response); 43 | 44 | setVideo(response.data[0]); 45 | form.reset(); 46 | } catch (error: any) { 47 | if (error?.response?.status === 403) proModal.onOpen(); 48 | else toast.error('Something went wrong!'); 49 | } finally { 50 | router.refresh(); 51 | } 52 | }; 53 | 54 | return ( 55 |
56 | 63 |
64 |
65 | 69 | ( 72 | 73 | 74 | 80 | 81 | 82 | )} 83 | /> 84 | 92 | 93 | 94 | {isLoading && ( 95 |
96 | 97 |
98 | )} 99 | {!video && !isLoading && } 100 | {video && ( 101 | 107 | )} 108 |
109 |
110 | ); 111 | }; 112 | 113 | export default VideoGeneratorPage; 114 | -------------------------------------------------------------------------------- /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 |