├── .eslintrc.json ├── .gitignore ├── README.md ├── components.json ├── next.config.mjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── prisma └── schema.prisma ├── public ├── logo.svg ├── next.svg └── vercel.svg ├── src ├── app │ ├── api │ │ ├── route.ts │ │ ├── upgrade │ │ │ └── checkout │ │ │ │ └── route.ts │ │ └── webhook │ │ │ └── route.ts │ ├── dashboard │ │ ├── [templateSlug] │ │ │ ├── _components │ │ │ │ └── editor.tsx │ │ │ └── page.tsx │ │ ├── _components │ │ │ ├── ai-chart.tsx │ │ │ ├── ai-usage.tsx │ │ │ ├── categories.tsx │ │ │ ├── category-item.tsx │ │ │ ├── search-dashboard.tsx │ │ │ ├── sidebar.tsx │ │ │ └── template-list.tsx │ │ ├── history │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── page.tsx │ │ └── upgrade │ │ │ └── page.tsx │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ └── page.tsx ├── components │ ├── auth.tsx │ ├── logo.tsx │ └── ui │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── chart.tsx │ │ ├── input.tsx │ │ ├── table.tsx │ │ └── textarea.tsx ├── lib │ ├── content-templates.ts │ ├── db.ts │ ├── gemini-ai.ts │ └── utils.ts └── middleware.ts ├── tailwind.config.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | .env 4 | # dependencies 5 | /node_modules 6 | /.pnp 7 | .pnp.js 8 | .yarn/install-state.gz 9 | 10 | # testing 11 | /coverage 12 | 13 | # next.js 14 | /.next/ 15 | /out/ 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # local env files 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | ![Thumbnail Studio DEV (13)](https://github.com/user-attachments/assets/35e433b0-d660-4c39-96f6-5079fd18c3d4) 3 | 4 | 5 | ## Getting Started 6 | 7 | First, run the development server: 8 | 9 | ```bash 10 | npm run dev 11 | # or 12 | yarn dev 13 | # or 14 | pnpm dev 15 | # or 16 | bun dev 17 | ``` 18 | 19 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 20 | 21 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 22 | 23 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 24 | 25 | ## Learn More 26 | 27 | To learn more about Next.js, take a look at the following resources: 28 | 29 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 30 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 31 | 32 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 33 | 34 | ## Deploy on Vercel 35 | 36 | 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. 37 | 38 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 39 | # magic-social-ai-access 40 | -------------------------------------------------------------------------------- /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.ts", 8 | "css": "src/app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "magic-social-yt", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "prisma generate && next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@clerk/nextjs": "^5.2.3", 13 | "@google/generative-ai": "^0.14.1", 14 | "@prisma/client": "^5.16.2", 15 | "@radix-ui/react-slot": "^1.1.0", 16 | "axios": "^1.7.2", 17 | "class-variance-authority": "^0.7.0", 18 | "clsx": "^2.1.1", 19 | "lucide-react": "^0.408.0", 20 | "next": "14.2.5", 21 | "prisma": "^5.16.2", 22 | "query-string": "^9.0.0", 23 | "react": "^18", 24 | "react-dom": "^18", 25 | "react-icons": "^5.2.1", 26 | "react-quill": "^2.0.0", 27 | "recharts": "^2.12.7", 28 | "stripe": "^16.2.0", 29 | "tailwind-merge": "^2.4.0", 30 | "tailwindcss-animate": "^1.0.7" 31 | }, 32 | "devDependencies": { 33 | "@types/node": "^20", 34 | "@types/react": "^18", 35 | "@types/react-dom": "^18", 36 | "date-fns": "^3.6.0", 37 | "eslint": "^8", 38 | "eslint-config-next": "14.2.5", 39 | "postcss": "^8", 40 | "tailwindcss": "^3.4.1", 41 | "typescript": "^5" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /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 | // Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? 5 | // Try Prisma Accelerate: https://pris.ly/cli/accelerate-init 6 | 7 | generator client { 8 | provider = "prisma-client-js" 9 | } 10 | 11 | datasource db { 12 | provider = "postgresql" 13 | url = env("DATABASE_URL") 14 | } 15 | 16 | model AIOutput { 17 | id String @id @default(uuid()) 18 | userId String 19 | title String? 20 | description String 21 | templateUsed String 22 | createdAt DateTime @default(now()) 23 | } 24 | 25 | model Purchase { 26 | id String @id @default(uuid()) 27 | userId String 28 | credit Float 29 | createAt DateTime @default(now()) 30 | } 31 | 32 | model StripeCustomer { 33 | id String @id @default(uuid()) 34 | userId String @unique 35 | stripeCustomerId String @unique 36 | 37 | createAt DateTime @default(now()) 38 | } 39 | 40 | model User { 41 | id String @id @unique @default(uuid()) 42 | userId String @unique 43 | 44 | totalCredit Float @default(10000) 45 | } 46 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/api/route.ts: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/db"; 2 | import { auth } from "@clerk/nextjs/server"; 3 | import { revalidatePath } from "next/cache"; 4 | import { NextResponse } from "next/server"; 5 | 6 | export async function POST(req: Request) { 7 | try { 8 | const { userId } = auth(); 9 | 10 | if (!userId) { 11 | return new NextResponse("User Not Authenticated", { status: 401 }); 12 | } 13 | 14 | const { title, description, templateUsed } = await req.json(); 15 | 16 | const createNewDoc = await db.aIOutput.create({ 17 | data: { 18 | userId: userId, 19 | title: title, 20 | description: description, 21 | templateUsed: templateUsed, 22 | }, 23 | }); 24 | 25 | revalidatePath("/"); 26 | 27 | return NextResponse.json(createNewDoc, { status: 200 }); 28 | } catch (error) { 29 | return new NextResponse("POST, NEW DOC ERROR", { status: 500 }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/app/api/upgrade/checkout/route.ts: -------------------------------------------------------------------------------- 1 | import Stripe from "stripe"; 2 | import { db } from "@/lib/db"; 3 | import { auth, currentUser } from "@clerk/nextjs/server"; 4 | import { NextResponse } from "next/server"; 5 | 6 | const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, { 7 | apiVersion: "2024-06-20", 8 | }); 9 | 10 | export async function POST(req: Request) { 11 | try { 12 | const { userId } = auth(); 13 | const user = await currentUser(); 14 | 15 | if (!userId) { 16 | return new NextResponse("Unauthorized", { status: 401 }); 17 | } 18 | 19 | const line_items: Stripe.Checkout.SessionCreateParams.LineItem[] = [ 20 | { 21 | quantity: 1, 22 | price_data: { 23 | currency: "USD", 24 | product_data: { 25 | name: "10,000 AI Credit", 26 | description: "all $10 worth of credit", 27 | }, 28 | unit_amount: 1000, 29 | }, 30 | }, 31 | ]; 32 | 33 | let purchase = await db.purchase.create({ 34 | data: { 35 | userId: userId, 36 | credit: 10000, 37 | }, 38 | }); 39 | 40 | let stripeCustomer = await db.stripeCustomer.findUnique({ 41 | where: { 42 | userId: userId, 43 | }, 44 | select: { 45 | stripeCustomerId: true, 46 | }, 47 | }); 48 | 49 | if (!stripeCustomer) { 50 | const customer = await stripe.customers.create({ 51 | email: user?.emailAddresses[0].emailAddress, 52 | }); 53 | 54 | let stripeCustomer = await db.stripeCustomer.create({ 55 | data: { 56 | userId: userId, 57 | stripeCustomerId: customer.id, 58 | }, 59 | }); 60 | } 61 | 62 | const session = await stripe.checkout.sessions.create({ 63 | customer: stripeCustomer?.stripeCustomerId, 64 | line_items, 65 | mode: "payment", 66 | success_url: `http://magic-social.vercel.app/dashboard/`, 67 | cancel_url: `http://magic-social.vercel.app/`, 68 | metadata: { 69 | userId: userId, 70 | }, 71 | }); 72 | 73 | return NextResponse.json({ url: session.url }); 74 | } catch (error) { 75 | return new NextResponse("Internal Error", { status: 500 }); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/app/api/webhook/route.ts: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/db"; 2 | import { headers } from "next/headers"; 3 | import { NextResponse } from "next/server"; 4 | import Stripe from "stripe"; 5 | 6 | const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, { 7 | apiVersion: "2024-06-20", 8 | }); 9 | 10 | export async function POST(req: Request) { 11 | const body = await req.text(); 12 | const sig = headers().get("stripe-signature"); 13 | 14 | let event; 15 | 16 | try { 17 | event = stripe.webhooks.constructEvent( 18 | body, 19 | sig!, 20 | process.env.STRIPE_WEBHOOK_SECRET! 21 | ); 22 | } catch (error) { 23 | return NextResponse.json({ error: "Invalid Signature" }, { status: 400 }); 24 | } 25 | 26 | const session = event.data.object as Stripe.Checkout.Session; 27 | const userId = session?.metadata?.userId; 28 | 29 | if (event.type === "checkout.session.completed") { 30 | if (!userId) { 31 | return new NextResponse("Invalid sesison", { status: 400 }); 32 | } 33 | 34 | try { 35 | const findUserByUserID = await db.user.findUnique({ 36 | where: { 37 | userId: userId, 38 | }, 39 | }); 40 | 41 | if (!findUserByUserID) { 42 | await db.user.create({ 43 | data: { 44 | userId: userId, 45 | totalCredit: 10000 + 10000, 46 | }, 47 | }); 48 | } else { 49 | await db.user.update({ 50 | where: { 51 | userId: userId, 52 | }, 53 | data: { 54 | totalCredit: findUserByUserID.totalCredit + 10000, 55 | }, 56 | }); 57 | } 58 | } catch (error) { 59 | return new NextResponse("Invalid User not authorized", { status: 500 }); 60 | } 61 | } else { 62 | return new NextResponse("Invalid event", { status: 200 }); 63 | } 64 | return new NextResponse("Success", { status: 200 }); 65 | } 66 | -------------------------------------------------------------------------------- /src/app/dashboard/[templateSlug]/_components/editor.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import dynamic from "next/dynamic"; 4 | import { useMemo } from "react"; 5 | import "react-quill/dist/quill.snow.css"; 6 | 7 | export const Editor = ({ value }: { value: string }) => { 8 | const ReactQuill = useMemo( 9 | () => dynamic(() => import("react-quill"), { ssr: false }), 10 | [] 11 | ); 12 | 13 | return ( 14 | 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /src/app/dashboard/[templateSlug]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Button } from "@/components/ui/button"; 4 | import { Input } from "@/components/ui/input"; 5 | import { Textarea } from "@/components/ui/textarea"; 6 | import { contentTemplates } from "@/lib/content-templates"; 7 | import { Loader } from "lucide-react"; 8 | import { useState } from "react"; 9 | import { Editor } from "./_components/editor"; 10 | import { chatSession } from "@/lib/gemini-ai"; 11 | import axios from "axios"; 12 | 13 | interface templateSlugProps { 14 | templateSlug: string; 15 | } 16 | 17 | const TemplatePage = ({ params }: { params: templateSlugProps }) => { 18 | const [isLoading, setisLoading] = useState(false); 19 | const [aiOutput, setAIOutput] = useState(""); 20 | 21 | const selectedTemplate = contentTemplates.find( 22 | (item) => item.slug === params.templateSlug 23 | ); 24 | 25 | const generateAIContent = async (formData: FormData) => { 26 | setisLoading(true); 27 | try { 28 | let dataSet = { 29 | title: formData.get("title"), 30 | description: formData.get("description"), 31 | }; 32 | 33 | const selectedPrompt = selectedTemplate?.aiPrompt; 34 | const finalAIPrompt = JSON.stringify(dataSet) + ", " + selectedPrompt; 35 | 36 | const result = await chatSession.sendMessage(finalAIPrompt); 37 | setAIOutput(result.response.text()); 38 | 39 | const response = await axios.post("/api/", { 40 | title: dataSet.title, 41 | description: result.response.text(), 42 | templateUsed: selectedTemplate?.name, 43 | }); 44 | console.log("response: " + response); 45 | setisLoading(false); 46 | } catch (error) { 47 | console.log(error); 48 | } 49 | }; 50 | const onSubmit = async (formData: FormData) => { 51 | generateAIContent(formData); 52 | }; 53 | return ( 54 |
55 |
56 |

{selectedTemplate?.name}

57 |
58 | 59 |
60 |
61 | {selectedTemplate?.form?.map((form) => ( 62 |
63 | 64 | {form.field === "input" ? ( 65 |
66 | 67 |
68 | ) : ( 69 |
70 |