├── .commitlintrc.json ├── .eslintrc.json ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .nvmrc ├── .prettierignore ├── README.md ├── actions ├── generate-user-stripe.ts └── update-user-name.ts ├── app ├── (auth) │ ├── layout.tsx │ ├── login │ │ └── page.tsx │ └── register │ │ └── page.tsx ├── (dashboard) │ ├── dashboard │ │ ├── actions.ts │ │ ├── activity │ │ │ ├── activity-log.tsx │ │ │ ├── campaign-selector.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ ├── billing │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ ├── campaigns │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ ├── integrations │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── loading.tsx │ │ ├── notifications │ │ │ └── page.tsx │ │ ├── page.tsx │ │ └── settings │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ └── playground │ │ ├── form.tsx │ │ ├── layout.tsx │ │ ├── page.tsx │ │ └── search-results.tsx ├── (marketing) │ ├── components │ │ ├── 3DDemo.tsx │ │ ├── CallToAction.tsx │ │ ├── FeatureSection.tsx │ │ ├── HeroSection.tsx │ │ ├── HowItWorksSection.tsx │ │ ├── MakerContent.tsx │ │ ├── MobileSection.tsx │ │ ├── PricingSection.tsx │ │ ├── ProofSuccess.tsx │ │ ├── TabButtons.tsx │ │ └── page.tsx │ ├── error.tsx │ ├── layout.tsx │ ├── page.tsx │ └── pricing │ │ ├── loading.tsx │ │ └── page.tsx ├── (project) │ └── project │ │ ├── _components │ │ ├── client-tab-wrapper.tsx │ │ ├── delete-post-modal.tsx │ │ ├── edit-comment.tsx │ │ ├── find-content-button.tsx │ │ ├── generate-comment.tsx │ │ └── post-comment.tsx │ │ ├── activity │ │ ├── activity-log.tsx │ │ ├── loading.tsx │ │ └── page.tsx │ │ ├── explorer │ │ ├── components │ │ │ ├── campaign-selector.tsx │ │ │ ├── keyword-management-modal.tsx │ │ │ ├── playground-results-display.tsx │ │ │ └── playground-settings-form.tsx │ │ ├── data │ │ │ ├── models.ts │ │ │ └── presets.ts │ │ └── page.tsx │ │ ├── keywords │ │ ├── loading.tsx │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── loading.tsx │ │ ├── page.tsx │ │ ├── playground │ │ ├── components │ │ │ └── form.tsx │ │ └── page.tsx │ │ ├── preferences │ │ ├── loading.tsx │ │ └── page.tsx │ │ └── settings │ │ ├── loading.tsx │ │ └── page.tsx ├── actions │ ├── activity-log.tsx │ ├── ai.ts │ ├── apify.ts │ ├── campaign.ts │ ├── comment.ts │ ├── email.ts │ ├── fetch-linkedin.ts │ ├── index.ts │ ├── keywords.ts │ ├── notification.ts │ ├── post-preferences.ts │ ├── post.ts │ └── twitter.ts ├── api │ ├── auth │ │ ├── [...nextauth] │ │ │ └── route.ts │ │ └── linkedin │ │ │ └── callback │ │ │ └── route.ts │ ├── callback │ │ ├── linkedin │ │ │ └── route.ts │ │ ├── route.ts │ │ └── twitter │ │ │ └── route.ts │ ├── cron │ │ └── campaign │ │ │ ├── comment │ │ │ └── route.ts │ │ │ └── post │ │ │ └── route.ts │ ├── linkedin │ │ └── route.ts │ ├── og │ │ └── route.tsx │ ├── openai │ │ ├── openai.ts │ │ └── route.ts │ ├── reddit │ │ └── route.ts │ ├── search │ │ └── route.ts │ ├── webhooks │ │ └── stripe │ │ │ └── route.ts │ └── x │ │ └── route.ts ├── layout.tsx ├── opengraph-image.jpg └── robots.ts ├── assets └── fonts │ ├── CalSans-SemiBold.ttf │ ├── CalSans-SemiBold.woff2 │ ├── Inter-Bold.ttf │ ├── Inter-Regular.ttf │ └── index.ts ├── components.json ├── components ├── 3d-pin.tsx ├── analytics.tsx ├── bentogrid.tsx ├── billing-info.tsx ├── blog-posts.tsx ├── browser.tsx ├── connect-linkedin-button.tsx ├── connect-linkedin-card.tsx ├── connect-twitter-button.tsx ├── connect-twitter-card.tsx ├── content │ ├── mdx-card.tsx │ └── mdx-components.tsx ├── dashboard │ ├── activity-log │ │ ├── columns.tsx │ │ ├── data-table.tsx │ │ └── index.tsx │ ├── add-campaign-modal.tsx │ ├── add-keywords-modal.tsx │ ├── add-post-preferences-moda.tsx │ ├── autopilot-switch.tsx │ ├── campaign-menu.tsx │ ├── campaigns.tsx │ ├── collapsible-server.tsx │ ├── empty-campaign-card.tsx │ ├── header.tsx │ ├── keyword-select-form.tsx │ ├── post-preferences-form.tsx │ ├── shell.tsx │ └── subscription-modal.tsx ├── disconnect-linkedin-button.tsx ├── docs │ ├── page-header.tsx │ ├── search.tsx │ └── sidebar-nav.tsx ├── features.tsx ├── feedback.tsx ├── forms │ ├── apidojo-twitter-scrapper-form.tsx │ ├── billing-form-button.tsx │ ├── invite-user.tsx │ ├── microworlds-twitter-scrapper-form.tsx │ ├── repo.tsx │ ├── user-auth-form.tsx │ └── user-name-form.tsx ├── gif-card.tsx ├── howitworks.tsx ├── info-landing.tsx ├── layout │ ├── collapsible-demo.tsx │ ├── landing-steps.tsx │ ├── main-nav.tsx │ ├── marketing-section.tsx │ ├── mobile-nav.tsx │ ├── mode-toggle.tsx │ ├── nav.tsx │ ├── navbar.tsx │ ├── preview-select.tsx │ ├── project-requirements.tsx │ ├── select-worktype.tsx │ ├── sign-in-modal.tsx │ ├── site-footer.tsx │ ├── sticky-scroll.tsx │ ├── user-account-nav.tsx │ └── user-explain.tsx ├── linkedin-card.tsx ├── magicui │ └── sparkles-text.tsx ├── modal-provider.tsx ├── pricing-cards.tsx ├── pricing-faq.tsx ├── project │ ├── form-modal.tsx │ ├── repo-dropdown.tsx │ └── submit-modal.tsx ├── providers.tsx ├── query-provider.tsx ├── radar │ ├── IconContainer.tsx │ ├── Preview.tsx │ └── Radar.tsx ├── shared │ ├── callout.tsx │ ├── card-skeleton.tsx │ ├── empty-placeholder.tsx │ ├── header-section.tsx │ ├── icons.tsx │ ├── modal.tsx │ ├── toc.tsx │ └── user-avatar.tsx ├── tailwind-indicator.tsx ├── timeline.tsx ├── twitter-card.tsx ├── ugc-text.tsx └── ui │ ├── 3d-card.tsx │ ├── 3d-pin.tsx │ ├── accordion.tsx │ ├── alert-dialog.tsx │ ├── alert.tsx │ ├── animated-tooltip.tsx │ ├── aspect-ratio.tsx │ ├── avatar.tsx │ ├── badge.tsx │ ├── bento-grid.tsx │ ├── button.tsx │ ├── calendar.tsx │ ├── card.tsx │ ├── checkbox.tsx │ ├── collapsible.tsx │ ├── command.tsx │ ├── container-scroll-animation.tsx │ ├── context-menu.tsx │ ├── dialog.tsx │ ├── drawer.tsx │ ├── dropdown-menu.tsx │ ├── fancy-tabs.tsx │ ├── form.tsx │ ├── hero-highlight.tsx │ ├── hero-parallax.tsx │ ├── hover-card.tsx │ ├── input.tsx │ ├── label.tsx │ ├── macbook-scroll.tsx │ ├── menubar.tsx │ ├── moving-border.tsx │ ├── navigation-menu.tsx │ ├── popover.tsx │ ├── progress.tsx │ ├── radio-group.tsx │ ├── resizable.tsx │ ├── scroll-area.tsx │ ├── select.tsx │ ├── separator.tsx │ ├── sheet.tsx │ ├── skeleton.tsx │ ├── slider.tsx │ ├── sonner.tsx │ ├── stepper │ ├── context.tsx │ ├── horizontal-step.tsx │ ├── index.tsx │ ├── step-button-container.tsx │ ├── step-icon.tsx │ ├── step-label.tsx │ ├── step.tsx │ ├── types.ts │ ├── use-media-query.tsx │ ├── use-stepper.ts │ └── vertical-step.tsx │ ├── sticky-scroll-reveal.tsx │ ├── switch.tsx │ ├── table.tsx │ ├── tabs.tsx │ ├── textarea.tsx │ ├── toast.tsx │ ├── toaster.tsx │ ├── toggle.tsx │ ├── tooltip.tsx │ ├── tracing-beam.tsx │ ├── tweet-card.tsx │ └── use-toast.ts ├── config ├── dashboard.ts ├── docs.ts ├── landing.ts ├── marketing.ts ├── site.ts └── subscriptions.ts ├── content ├── authors │ └── shadcn.mdx ├── blog │ ├── deploying-next-apps.mdx │ ├── dynamic-routing-static-regeneration.mdx │ ├── preview-mode-headless-cms.mdx │ └── server-client-components.mdx ├── docs │ ├── documentation │ │ ├── code-blocks.mdx │ │ ├── components.mdx │ │ ├── index.mdx │ │ └── style-guide.mdx │ ├── in-progress.mdx │ └── index.mdx ├── guides │ ├── build-blog-using-contentlayer-mdx.mdx │ └── using-next-auth-next-13.mdx └── pages │ ├── privacy.mdx │ └── terms.mdx ├── emails ├── autopilot-on.tsx ├── magic-link-email.tsx ├── weekly-summary.tsx └── welcome-email.tsx ├── env.mjs ├── hooks ├── use-intersection-observer.ts ├── use-keywords-modal.tsx ├── use-local-storage.ts ├── use-lock-body.ts ├── use-media-query.ts ├── use-mounted.ts ├── use-mutation-observer.ts ├── use-post-preferences.tsx ├── use-scroll.ts ├── use-signin-modal.ts └── use-tab-store.ts ├── lib ├── apify.ts ├── auth.ts ├── config │ └── index.ts ├── db.ts ├── email.ts ├── encryption.ts ├── exceptions.ts ├── linkedin-api │ ├── access_token.json │ └── index.ts ├── linkedin.ts ├── queries.ts ├── reddit.ts ├── reddit │ └── reddit.ts ├── session.ts ├── stripe.ts ├── subscription.ts ├── toc.ts ├── twitter.ts ├── utils.ts └── validations │ ├── auth.ts │ ├── og.ts │ ├── twitter.ts │ └── user.ts ├── middleware.ts ├── next.config.js ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── prisma ├── migrations │ └── 0_init │ │ └── migration.sql ├── schema.prisma └── seed.ts ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── cases │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ └── 5.png ├── creates.gif ├── demo.gif ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── images │ ├── 400x400.jpg │ ├── avatars │ │ └── shadcn.png │ ├── blog │ │ ├── blog-post-1.jpg │ │ ├── blog-post-2.jpg │ │ ├── blog-post-3.jpg │ │ └── blog-post-4.jpg │ ├── hero.png │ ├── user-step1.gif │ ├── user-step2.gif │ └── user-step3.gif ├── og.jpg ├── site.webmanifest ├── steps │ ├── step1.gif │ ├── step2.gif │ └── step3.gif └── vercel.svg ├── schemas ├── apify.ts ├── campaign.ts ├── content-interaction.ts ├── logging-notification.ts ├── notification.ts └── playground.ts ├── styles ├── globals.css └── mdx.css ├── tailwind.config.js ├── tsconfig.json ├── types ├── env.d.ts ├── index.d.ts ├── next-auth.d.ts └── reddit.ts ├── utils └── cn.ts └── vercel.json /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/eslintrc", 3 | "root": true, 4 | "extends": [ 5 | "next/core-web-vitals", 6 | "prettier", 7 | "plugin:tailwindcss/recommended" 8 | ], 9 | "plugins": ["tailwindcss"], 10 | "rules": { 11 | "@next/next/no-html-link-for-pages": "off", 12 | "react/jsx-key": "off", 13 | "tailwindcss/no-custom-classname": "off", 14 | "tailwindcss/classnames-order": "off" 15 | }, 16 | "settings": { 17 | "tailwindcss": { 18 | "callees": ["cn"], 19 | "config": "tailwind.config.js" 20 | }, 21 | "next": { 22 | "rootDir": true 23 | } 24 | }, 25 | "overrides": [ 26 | { 27 | "files": ["*.ts", "*.tsx"], 28 | "parser": "@typescript-eslint/parser" 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /.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 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | .env 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | next-env.d.ts 38 | 39 | # email 40 | /.react-email/ 41 | 42 | .vscode 43 | .contentlayer -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx pretty-quick --staged 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.18.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .next 4 | build 5 | .contentlayer -------------------------------------------------------------------------------- /actions/update-user-name.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { authOptions } from "@/lib/auth"; 4 | import { prisma } from "@/lib/db"; 5 | import { userNameSchema } from "@/lib/validations/user"; 6 | import { getServerSession } from "next-auth"; 7 | import { revalidatePath } from "next/cache"; 8 | 9 | export type FormData = { 10 | name: string; 11 | }; 12 | 13 | export async function updateUserName(userId: string, data: FormData) { 14 | try { 15 | const session = await getServerSession(authOptions) 16 | 17 | if (!session?.user || session?.user.id !== userId) { 18 | throw new Error("Unauthorized"); 19 | } 20 | 21 | const { name } = userNameSchema.parse(data); 22 | 23 | // Update the user name. 24 | await prisma.user.update({ 25 | where: { 26 | id: userId, 27 | }, 28 | data: { 29 | name: name, 30 | }, 31 | }) 32 | 33 | revalidatePath('/dashboard/settings'); 34 | return { status: "success" }; 35 | } catch (error) { 36 | console.log(error) 37 | return { status: "error" } 38 | } 39 | } -------------------------------------------------------------------------------- /app/(auth)/layout.tsx: -------------------------------------------------------------------------------- 1 | interface AuthLayoutProps { 2 | children: React.ReactNode 3 | } 4 | 5 | export default function AuthLayout({ children }: AuthLayoutProps) { 6 | return
{children}
7 | } 8 | -------------------------------------------------------------------------------- /app/(auth)/login/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next" 2 | import Link from "next/link" 3 | 4 | import { cn } from "@/lib/utils" 5 | import { buttonVariants } from "@/components/ui/button" 6 | import { Icons } from "@/components/shared/icons" 7 | import { UserAuthForm } from "@/components/forms/user-auth-form" 8 | import { Suspense } from "react" 9 | 10 | export const metadata: Metadata = { 11 | title: "Login", 12 | description: "Login to your account", 13 | } 14 | 15 | export default function LoginPage() { 16 | return ( 17 |
18 | 25 | <> 26 | 27 | Back 28 | 29 | 30 |
31 |
32 | 33 |

34 | Welcome back 35 |

36 |

37 | Enter your email to sign in to your account 38 |

39 |
40 | 41 | 42 | 43 |

44 | 48 | Don't have an account? Sign Up 49 | 50 |

51 |
52 |
53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /app/(auth)/register/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link" 2 | 3 | import { cn } from "@/lib/utils" 4 | import { buttonVariants } from "@/components/ui/button" 5 | import { Icons } from "@/components/shared/icons" 6 | import { UserAuthForm } from "@/components/forms/user-auth-form" 7 | import { Suspense } from "react" 8 | 9 | export const metadata = { 10 | title: "Create an account", 11 | description: "Create an account to get started.", 12 | } 13 | 14 | export default function RegisterPage() { 15 | return ( 16 |
17 | 24 | Login 25 | 26 |
27 |
28 |
29 |
30 | 31 |

32 | Create an account 33 |

34 |

35 | Enter your email below to create your account 36 |

37 |
38 | 39 | 40 | 41 |

42 | By clicking continue, you agree to our{" "} 43 | 47 | Terms of Service 48 | {" "} 49 | and{" "} 50 | 54 | Privacy Policy 55 | 56 | . 57 |

58 |
59 |
60 |
61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /app/(dashboard)/dashboard/activity/loading.tsx: -------------------------------------------------------------------------------- 1 | import { CardSkeleton } from "@/components/shared/card-skeleton" 2 | import { DashboardHeader } from "@/components/dashboard/header" 3 | import { DashboardShell } from "@/components/dashboard/shell" 4 | 5 | export default function DashboardBillingLoading() { 6 | return ( 7 | 8 | 12 |
13 | 14 |
15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /app/(dashboard)/dashboard/activity/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation"; 2 | import { Campaign } from "@prisma/client"; 3 | 4 | import { authOptions } from "@/lib/auth"; 5 | import { getCurrentUser } from "@/lib/session"; 6 | import { DashboardHeader } from "@/components/dashboard/header"; 7 | import { DashboardShell } from "@/components/dashboard/shell"; 8 | import { getAllCampaigns } from "@/app/actions/campaign"; 9 | 10 | import { ActivityLogTable } from "./activity-log"; 11 | 12 | export const metadata = { 13 | title: "BuzzDaddy Overview", 14 | description: "Manage account and website settings.", 15 | }; 16 | 17 | export default async function SettingsPage() { 18 | const user = await getCurrentUser(); 19 | 20 | if (!user) { 21 | redirect(authOptions?.pages?.signIn || "/login"); 22 | } 23 | 24 | const campaignsResult = await getAllCampaigns(user.id); 25 | 26 | if (campaignsResult.type === "error") { 27 | throw new Error(campaignsResult.message); 28 | } 29 | 30 | const campaigns: Campaign[] = campaignsResult.data || []; 31 | 32 | return ( 33 | 34 | 38 |
39 | ({ 41 | id: campaign.id, 42 | name: campaign.name, 43 | }))} 44 | /> 45 |
46 |
47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /app/(dashboard)/dashboard/billing/loading.tsx: -------------------------------------------------------------------------------- 1 | import { CardSkeleton } from "@/components/shared/card-skeleton" 2 | import { DashboardHeader } from "@/components/dashboard/header" 3 | import { DashboardShell } from "@/components/dashboard/shell" 4 | 5 | export default function DashboardBillingLoading() { 6 | return ( 7 | 8 | 12 |
13 | 14 |
15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /app/(dashboard)/dashboard/billing/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation"; 2 | 3 | import { authOptions } from "@/lib/auth"; 4 | import { getCurrentUser } from "@/lib/session"; 5 | import { getUserSubscriptionPlan } from "@/lib/subscription"; 6 | import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; 7 | import { BillingInfo } from "@/components/billing-info"; 8 | import { DashboardHeader } from "@/components/dashboard/header"; 9 | import { DashboardShell } from "@/components/dashboard/shell"; 10 | import { Icons } from "@/components/shared/icons"; 11 | 12 | export const metadata = { 13 | title: "Billing", 14 | description: "Manage billing and your subscription plan.", 15 | }; 16 | 17 | export default async function BillingPage() { 18 | const user = await getCurrentUser(); 19 | 20 | if (!user) { 21 | redirect(authOptions?.pages?.signIn || "/login"); 22 | } 23 | 24 | const subscriptionPlan = await getUserSubscriptionPlan(user.id); 25 | 26 | console.log("user", user); 27 | console.log("subscriptionPlan", subscriptionPlan); 28 | 29 | return ( 30 | 31 | 35 |
36 | 37 | 38 | This is a demo app. 39 | 40 | BuzzDaddy is a demo app using a Stripe test environment. You can 41 | find a list of test card numbers on the{" "} 42 | 48 | Stripe docs 49 | 50 | . 51 | 52 | 53 | 54 |
55 |
56 | ); 57 | } 58 | -------------------------------------------------------------------------------- /app/(dashboard)/dashboard/campaigns/loading.tsx: -------------------------------------------------------------------------------- 1 | import { CardSkeleton } from "@/components/shared/card-skeleton" 2 | import { DashboardHeader } from "@/components/dashboard/header" 3 | import { DashboardShell } from "@/components/dashboard/shell" 4 | 5 | export default function DashboardSettingsLoading() { 6 | return ( 7 | 8 | 9 |
10 | 11 |
12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /app/(dashboard)/dashboard/campaigns/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation"; 2 | 3 | import { authOptions } from "@/lib/auth"; 4 | import { getCurrentUser } from "@/lib/session"; 5 | import { Campaigns } from "@/components/dashboard/campaigns"; 6 | import { DashboardHeader } from "@/components/dashboard/header"; 7 | import { DashboardShell } from "@/components/dashboard/shell"; 8 | // import { AddCampaignModal } from "@/components/dashboard/add-campaign-modal"; 9 | import { getAllCampaigns } from "@/app/actions/campaign"; 10 | 11 | export const metadata = { 12 | title: "BuzzDaddy Overview", 13 | description: "Manage account and website settings.", 14 | }; 15 | 16 | export default async function SettingsPage() { 17 | const user = await getCurrentUser(); 18 | 19 | if (!user) { 20 | redirect(authOptions?.pages?.signIn || "/login"); 21 | } 22 | 23 | const result = await getAllCampaigns(user.id); 24 | 25 | if (result.type === "error" || !result.data) { 26 | console.error(result.message); 27 | return null; 28 | } 29 | 30 | return ( 31 | 32 | 33 | {/* */} 34 | 35 | 36 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /app/(dashboard)/dashboard/integrations/loading.tsx: -------------------------------------------------------------------------------- 1 | import { DashboardHeader } from "@/components/dashboard/header"; 2 | import { DashboardShell } from "@/components/dashboard/shell"; 3 | import { CardSkeleton } from "@/components/shared/card-skeleton"; 4 | 5 | export default function DashboardBillingLoading() { 6 | return ( 7 | 8 | 12 |
13 | 14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /app/(dashboard)/dashboard/integrations/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation"; 2 | 3 | import { authOptions } from "@/lib/auth"; 4 | import { getLinkedInToken, getTwitterToken } from "@/lib/queries"; 5 | import { getCurrentUser } from "@/lib/session"; 6 | import { ConnectLinkedInCard } from "@/components/connect-linkedin-card"; 7 | import { ConnectTwitterCard } from "@/components/connect-twitter-card"; 8 | import { DashboardHeader } from "@/components/dashboard/header"; 9 | import { DashboardShell } from "@/components/dashboard/shell"; 10 | import { LinkedInCard } from "@/components/linkedin-card"; 11 | import { TwitterCard } from "@/components/twitter-card"; 12 | 13 | export const metadata = { 14 | title: "Integrations", 15 | description: "Manage your app integrations.", 16 | }; 17 | 18 | export default async function IntegrationsPage() { 19 | const user = await getCurrentUser(); 20 | 21 | if (!user) { 22 | redirect(authOptions?.pages?.signIn || "/login"); 23 | } 24 | 25 | const twitterToken = await getTwitterToken(user.id); 26 | const twitterAccessToken = twitterToken?.accessToken 27 | ? twitterToken.accessToken 28 | : ""; 29 | 30 | const linkedToken = await getLinkedInToken(user.id); 31 | const linkedinAccessToken = linkedToken?.accessToken 32 | ? linkedToken.accessToken 33 | : ""; 34 | 35 | return ( 36 | 37 | 41 |
42 | {twitterAccessToken ? ( 43 | 44 | ) : ( 45 | <> 46 | 47 | 48 | )} 49 | 50 | {linkedinAccessToken ? ( 51 | 52 | ) : ( 53 | 54 | )} 55 |
56 |
57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /app/(dashboard)/dashboard/layout.tsx: -------------------------------------------------------------------------------- 1 | import { notFound } from "next/navigation"; 2 | 3 | import { dashboardConfig } from "@/config/dashboard"; 4 | import { getCurrentUser } from "@/lib/session"; 5 | import { NestedNav } from "@/components/layout/nav"; 6 | import { NavBar } from "@/components/layout/navbar"; 7 | import { SiteFooter } from "@/components/layout/site-footer"; 8 | 9 | interface DashboardLayoutProps { 10 | children?: React.ReactNode; 11 | } 12 | 13 | export default async function DashboardLayout({ 14 | children, 15 | }: DashboardLayoutProps) { 16 | const user = await getCurrentUser(); 17 | 18 | if (!user) { 19 | return notFound(); 20 | } 21 | 22 | return ( 23 |
24 | 25 | 26 |
27 | 30 |
31 | {children} 32 |
33 |
34 | 35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /app/(dashboard)/dashboard/loading.tsx: -------------------------------------------------------------------------------- 1 | import { CardSkeleton } from "@/components/shared/card-skeleton" 2 | import { DashboardHeader } from "@/components/dashboard/header" 3 | import { DashboardShell } from "@/components/dashboard/shell" 4 | import { Button } from "@/components/ui/button" 5 | 6 | export default function DashboardLoading() { 7 | return ( 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 |
18 |
19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /app/(dashboard)/dashboard/notifications/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation" 2 | 3 | import { BillingInfo } from "@/components/billing-info" 4 | import { DashboardHeader } from "@/components/dashboard/header" 5 | import { DashboardShell } from "@/components/dashboard/shell" 6 | import { Icons } from "@/components/shared/icons" 7 | import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" 8 | import { authOptions } from "@/lib/auth" 9 | import { getCurrentUser } from "@/lib/session" 10 | import { getUserSubscriptionPlan } from "@/lib/subscription" 11 | 12 | export const metadata = { 13 | title: "Activity Log", 14 | description: "Your activity log and notifications.", 15 | } 16 | 17 | export default async function BillingPage() { 18 | const user = await getCurrentUser() 19 | 20 | if (!user) { 21 | redirect(authOptions?.pages?.signIn || "/login") 22 | } 23 | 24 | const subscriptionPlan = await getUserSubscriptionPlan(user.id) 25 | 26 | return ( 27 | 28 | 32 |
33 | 34 | 35 | This is a demo app. 36 | 37 | BuzzDaddy is a demo app using a Stripe test environment. You can 38 | find a list of test card numbers on the{" "} 39 | 45 | Stripe docs 46 | 47 | . 48 | 49 | 50 | 53 |
54 |
55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /app/(dashboard)/dashboard/settings/loading.tsx: -------------------------------------------------------------------------------- 1 | import { CardSkeleton } from "@/components/shared/card-skeleton" 2 | import { DashboardHeader } from "@/components/dashboard/header" 3 | import { DashboardShell } from "@/components/dashboard/shell" 4 | 5 | export default function DashboardSettingsLoading() { 6 | return ( 7 | 8 | 12 |
13 | 14 |
15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /app/(dashboard)/dashboard/settings/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation" 2 | 3 | import { authOptions } from "@/lib/auth" 4 | import { getCurrentUser } from "@/lib/session" 5 | import { DashboardHeader } from "@/components/dashboard/header" 6 | import { DashboardShell } from "@/components/dashboard/shell" 7 | import { UserNameForm } from "@/components/forms/user-name-form" 8 | import { InviteUserForm } from "@/components/forms/invite-user" 9 | 10 | export const metadata = { 11 | title: "Settings", 12 | description: "Manage account and website settings.", 13 | } 14 | 15 | export default async function SettingsPage() { 16 | const user = await getCurrentUser() 17 | 18 | if (!user) { 19 | redirect(authOptions?.pages?.signIn || "/login") 20 | } 21 | 22 | return ( 23 | 24 | 28 |
29 | 30 | 31 |
32 |
33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /app/(dashboard)/playground/layout.tsx: -------------------------------------------------------------------------------- 1 | import { notFound } from "next/navigation"; 2 | 3 | import { dashboardConfig } from "@/config/dashboard"; 4 | import { getCurrentUser } from "@/lib/session"; 5 | import { NavBar } from "@/components/layout/navbar"; 6 | import { SiteFooter } from "@/components/layout/site-footer"; 7 | 8 | interface PlaygroundLayoutProps { 9 | children?: React.ReactNode; 10 | } 11 | 12 | export default async function PlaygroundLayout({ 13 | children, 14 | }: PlaygroundLayoutProps) { 15 | const user = await getCurrentUser(); 16 | 17 | if (!user) { 18 | return notFound(); 19 | } 20 | 21 | return ( 22 |
23 | 24 | 25 |
{children}
26 | 27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /app/(dashboard)/playground/search-results.tsx: -------------------------------------------------------------------------------- 1 | export const dynamic = "force-dynamic"; 2 | 3 | interface SearchResult { 4 | title: string; 5 | link: string; 6 | snippet: string; 7 | } 8 | 9 | const SearchResults = ({ 10 | searchParams, 11 | results, 12 | }: { 13 | searchParams: { [key: string]: string }; 14 | results: SearchResult[]; 15 | }) => { 16 | return ( 17 |
18 | {results.length > 0 ? ( 19 |
20 |

Search Results:

21 | 36 |
37 | ) : ( 38 |
39 | Try searching for something using using different keywords on the 40 | left. 41 |
42 | )} 43 |
44 | ); 45 | }; 46 | 47 | export default SearchResults; 48 | -------------------------------------------------------------------------------- /app/(marketing)/components/CallToAction.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Button } from "@/components/ui/moving-border"; 3 | 4 | const CallToAction = ({ onClick }) => ( 5 |
9 | 16 |
17 | ); 18 | 19 | export default CallToAction; 20 | -------------------------------------------------------------------------------- /app/(marketing)/components/FeatureSection.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BentoGrid } from "@/components/bentogrid"; 3 | 4 | const FeatureSection = () => ( 5 |
9 |
10 |

11 | Features 12 |

13 |

14 | Leverage our low-cost automation tool to promote your business 24/7. BuzzDaddy will mention you naturally in recent & relevant posts. 15 |

16 |
17 | 18 |
19 | ); 20 | 21 | export default FeatureSection; 22 | -------------------------------------------------------------------------------- /app/(marketing)/components/HeroSection.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const HeroSection = ({ signInModal }) => ( 4 |
5 |
6 |

10 | Name-drop your business across social media 24/7 with BuzzDaddy to boost sales 11 |

12 |

16 | Meet your customizable bot that replies to public X, LinkedIn & Reddit posts on your behalf - generating organic buzz for your business. 17 |

18 |
19 |
20 | ); 21 | 22 | export default HeroSection; 23 | -------------------------------------------------------------------------------- /app/(marketing)/components/MakerContent.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSigninModal } from "@/hooks/use-signin-modal"; 3 | import HeroSection from "./HeroSection"; 4 | import CallToAction from "./CallToAction"; 5 | import HowItWorksSection from "./HowItWorksSection"; 6 | import FeatureSection from "./FeatureSection"; 7 | import PricingSection from "./PricingSection"; 8 | import PreviewRadar from "@/components/radar/Preview"; 9 | import YoutubeSection from "./ProofSuccess"; 10 | import ThreeDComponent from "./3DDemo"; 11 | import MobileSection from "./MobileSection"; 12 | 13 | const MakerContent = () => { 14 | const signInModal = useSigninModal(); 15 | return ( 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | ); 28 | }; 29 | 30 | export default MakerContent; 31 | -------------------------------------------------------------------------------- /app/(marketing)/components/MobileSection.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { InfoLanding } from "@/components/info-landing"; 3 | import { infos } from "@/config/landing"; // Assuming infos is defined here 4 | 5 | const MobileSection = () => { 6 | const gridPatternStyle = { 7 | backgroundImage: ` 8 | linear-gradient(to top right, rgba(9,9,11,255) 37%, rgba(0,0,0,0) 100%), 9 | linear-gradient(rgba(255,255,255,0.3) 1px, transparent 1px), 10 | linear-gradient(90deg, rgba(255,255,255,0.3) 1px, transparent 1px) 11 | `, 12 | backgroundSize: "cover, 110px 60px, 70px 40px", 13 | backgroundBlendMode: "normal, normal, normal", 14 | }; 15 | 16 | return ( 17 |
27 | {/* */} 28 | 29 |
30 | ); 31 | }; 32 | 33 | export default MobileSection; 34 | -------------------------------------------------------------------------------- /app/(marketing)/components/PricingSection.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { PricingFaq } from "@/components/pricing-faq"; 3 | 4 | const PricingSection = () => ( 5 |
6 | 7 |
8 | ); 9 | 10 | export default PricingSection; 11 | -------------------------------------------------------------------------------- /app/(marketing)/components/ProofSuccess.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ContainerScroll } from "@/components/ui/container-scroll-animation"; 3 | import { UGCText } from "@/components/ugc-text"; 4 | import Image from "next/image"; 5 | 6 | const images = [ 7 | '/cases/1.png', 8 | '/cases/2.png', 9 | '/cases/3.png', 10 | '/cases/4.png' 11 | ]; 12 | 13 | const YoutubeSection = () => { 14 | const gridPatternStyle = { 15 | backgroundImage: ` 16 | linear-gradient(to top right, rgba(9,9,11,255) 37%, rgba(0,0,0,0) 100%), 17 | linear-gradient(rgba(255,255,255,0.3) 1px, transparent 1px), 18 | linear-gradient(90deg, rgba(255,255,255,0.3) 1px, transparent 1px) 19 | `, 20 | backgroundSize: "cover, 110px 60px, 70px 40px", 21 | backgroundBlendMode: "normal, normal, normal", 22 | }; 23 | 24 | return ( 25 |
35 |
36 | 39 | 40 |

41 | People love our replies 😁 42 |

43 | 44 | } 45 | > 46 |
47 | {images.map((image, index) => ( 48 |
49 | {`Screenshot 50 |
51 | ))} 52 |
53 |
54 |
55 |
56 | ); 57 | }; 58 | 59 | export default YoutubeSection; 60 | -------------------------------------------------------------------------------- /app/(marketing)/components/TabButtons.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const TabButtons = ({ activeTab, setActiveTab }) => { 4 | const buttonClass = (tab) => 5 | `px-4 py-2 text-sm font-semibold transition duration-300 ${ 6 | activeTab === tab 7 | ? "text-secondary bg-primary" 8 | : "text-gray-300 bg-transparent" 9 | }`; 10 | 11 | return ( 12 |
13 | 20 | {/* Uncomment if you want the Developers tab */} 21 | {/* */} 28 |
29 | ); 30 | }; 31 | 32 | export default TabButtons; 33 | -------------------------------------------------------------------------------- /app/(marketing)/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Button } from '@/components/ui/button'; 4 | 5 | export default function Error({ 6 | reset, 7 | }: { 8 | reset: () => void; 9 | }) { 10 | 11 | return ( 12 |
13 |

Something went wrong!

14 | 21 |
22 | ); 23 | } -------------------------------------------------------------------------------- /app/(marketing)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { NavBar } from "@/components/layout/navbar" 2 | import { SiteFooter } from "@/components/layout/site-footer" 3 | import { marketingConfig } from "@/config/marketing" 4 | import { getCurrentUser } from "@/lib/session" 5 | import { Suspense } from "react" 6 | 7 | interface MarketingLayoutProps { 8 | children: React.ReactNode 9 | } 10 | 11 | export default async function MarketingLayout({ 12 | children, 13 | }: MarketingLayoutProps) { 14 | const user = await getCurrentUser() 15 | 16 | return ( 17 |
18 | 19 | 20 | 21 |
{children}
22 | 23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /app/(marketing)/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useState } from "react"; 4 | import TabButtons from "./components/TabButtons"; 5 | import MakerContent from "./components/MakerContent"; 6 | 7 | export default function IndexPage() { 8 | const [activeTab, setActiveTab] = useState("makers"); 9 | 10 | return ( 11 |
12 |
13 | 14 |
15 |
21 | {activeTab === "makers" && } 22 |
23 |
24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /app/(marketing)/pricing/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from "@/components/ui/skeleton" 2 | 3 | export default function Loading() { 4 | return ( 5 |
6 |
7 |
8 | 9 | 10 | 11 |
12 | 13 |
14 | 15 | 16 | 17 |
18 | 19 |
20 | 21 | 22 | 23 |
24 |
25 | 26 |
27 |
28 | ); 29 | } -------------------------------------------------------------------------------- /app/(marketing)/pricing/page.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { PricingCards } from '@/components/pricing-cards'; 3 | import { PricingFaq } from '@/components/pricing-faq'; 4 | import { Skeleton } from '@/components/ui/skeleton'; 5 | import { getCurrentUser } from '@/lib/session'; 6 | import { getUserSubscriptionPlan } from '@/lib/subscription'; 7 | 8 | export const metadata = { 9 | title: "Pricing", 10 | } 11 | 12 | export default async function PricingPage() { 13 | const user = await getCurrentUser() 14 | let subscriptionPlan; 15 | 16 | if (user) { 17 | subscriptionPlan = await getUserSubscriptionPlan(user.id) 18 | } 19 | 20 | return ( 21 |
22 | 23 |
24 | 25 |
26 | ) 27 | } -------------------------------------------------------------------------------- /app/(project)/project/_components/client-tab-wrapper.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | import { Platform } from "@/schemas/content-interaction"; 5 | import { FaLinkedinIn, FaRedditAlien, FaXTwitter } from "react-icons/fa6"; 6 | 7 | import { useTabStore } from "@/hooks/use-tab-store"; 8 | import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; 9 | 10 | export function ClientTabWrapper({ children }: { children: React.ReactNode }) { 11 | const { setActiveTab, activeTab } = useTabStore(); 12 | 13 | return ( 14 | { 17 | // console.log("Tab changed to:", value); 18 | setActiveTab(value as Platform); 19 | }} 20 | className="w-full" 21 | > 22 | 23 | 24 | 25 | Twitter 26 | 27 | 28 | 29 | LinkedIn 30 | 31 | 32 | 33 | Reddit 34 | 35 | 36 | {children} 37 | 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /app/(project)/project/_components/generate-comment.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRouter } from "next/navigation"; 4 | import { Campaign, Post } from "@prisma/client"; 5 | import { useMutation } from "@tanstack/react-query"; 6 | import { BsRobot } from "react-icons/bs"; 7 | import { toast } from "sonner"; 8 | 9 | import { Button } from "@/components/ui/button"; 10 | import { generatePostComment } from "@/app/actions/ai"; 11 | 12 | interface GenerateCommentProps { 13 | post: Post; 14 | campaign: Campaign; 15 | } 16 | 17 | export const GenerateComment = ({ post, campaign }: GenerateCommentProps) => { 18 | const router = useRouter(); 19 | 20 | const createNewComment = async () => { 21 | const result = await generatePostComment(post, campaign); 22 | 23 | if (result.type === "success") { 24 | return result.comment; 25 | } 26 | 27 | throw new Error("Failed to generate comment"); 28 | }; 29 | 30 | const { mutate, isPending } = useMutation({ 31 | mutationFn: createNewComment, 32 | onSuccess(data, variables, context) { 33 | console.log(data); 34 | toast.success("Comment generated successfully"); 35 | router.refresh(); 36 | }, 37 | onError(error, variables, context) { 38 | console.error(error); 39 | toast.error("Failed to generate comment"); 40 | }, 41 | }); 42 | 43 | return ( 44 | 54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /app/(project)/project/activity/loading.tsx: -------------------------------------------------------------------------------- 1 | import { CardSkeleton } from "@/components/shared/card-skeleton" 2 | import { DashboardHeader } from "@/components/dashboard/header" 3 | import { DashboardShell } from "@/components/dashboard/shell" 4 | 5 | export default function DashboardBillingLoading() { 6 | return ( 7 | 8 | 12 |
13 | 14 |
15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /app/(project)/project/activity/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation"; 2 | 3 | import { authOptions } from "@/lib/auth"; 4 | import { getCurrentUser } from "@/lib/session"; 5 | import { DashboardHeader } from "@/components/dashboard/header"; 6 | import { DashboardShell } from "@/components/dashboard/shell"; 7 | 8 | import { ActivityLogTable } from "./activity-log"; 9 | 10 | export const metadata = { 11 | title: "BuzzDaddy Overview", 12 | description: "Manage account and website settings.", 13 | }; 14 | 15 | export default async function SettingsPage() { 16 | const user = await getCurrentUser(); 17 | 18 | if (!user) { 19 | redirect(authOptions?.pages?.signIn || "/login"); 20 | } 21 | 22 | return ( 23 | 24 | 28 |
29 | 30 |
31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /app/(project)/project/explorer/data/presets.ts: -------------------------------------------------------------------------------- 1 | export interface Preset { 2 | id: string 3 | name: string 4 | } 5 | 6 | export const presets: Preset[] = [ 7 | { 8 | id: "9cb0e66a-9937-465d-a188-2c4c4ae2401f", 9 | name: "Twitter / X", 10 | }, 11 | { 12 | id: "61eb0e32-2391-4cd3-adc3-66efe09bc0b7", 13 | name: "Reddit", 14 | }, 15 | { 16 | id: "a4e1fa51-f4ce-4e45-892c-224030a00bdd", 17 | name: "Linkedin", 18 | }, 19 | // { 20 | // id: "cc198b13-4933-43aa-977e-dcd95fa30770", 21 | // name: "Q&A", 22 | // }, 23 | // { 24 | // id: "adfa95be-a575-45fd-a9ef-ea45386c64de", 25 | // name: "English to other languages", 26 | // }, 27 | // { 28 | // id: "c569a06a-0bd6-43a7-adf9-bf68c09e7a79", 29 | // name: "Parse unstructured data", 30 | // }, 31 | // { 32 | // id: "15ccc0d7-f37a-4f0a-8163-a37e162877dc", 33 | // name: "Classification", 34 | // }, 35 | // { 36 | // id: "4641ef41-1c0f-421d-b4b2-70fe431081f3", 37 | // name: "Natural language to Python", 38 | // }, 39 | // { 40 | // id: "48d34082-72f3-4a1b-a14d-f15aca4f57a0", 41 | // name: "Explain code", 42 | // }, 43 | // { 44 | // id: "dfd42fd5-0394-4810-92c6-cc907d3bfd1a", 45 | // name: "Chat", 46 | // }, 47 | ] 48 | -------------------------------------------------------------------------------- /app/(project)/project/keywords/loading.tsx: -------------------------------------------------------------------------------- 1 | import { DashboardHeader } from "@/components/dashboard/header"; 2 | import { DashboardShell } from "@/components/dashboard/shell"; 3 | import { CardSkeleton } from "@/components/shared/card-skeleton"; 4 | 5 | export default function DashboardBillingLoading() { 6 | return ( 7 | 8 | 12 |
13 | 14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /app/(project)/project/keywords/page.tsx: -------------------------------------------------------------------------------- 1 | import { SearchParams } from "@/types"; 2 | import { redirect } from "next/navigation"; 3 | 4 | import { DashboardHeader } from "@/components/dashboard/header"; 5 | import { KeywordSelectForm } from "@/components/dashboard/keyword-select-form"; 6 | import { DashboardShell } from "@/components/dashboard/shell"; 7 | import { authOptions } from "@/lib/auth"; 8 | import { getCurrentUser } from "@/lib/session"; 9 | 10 | export const metadata = { 11 | title: "BuzzDaddy Keywords Configuration", 12 | description: "Manage your keywords.", 13 | }; 14 | 15 | interface KeywordPageProps { 16 | searchParams: SearchParams<"id">; 17 | } 18 | 19 | export default async function SettingsPage({ searchParams }: KeywordPageProps) { 20 | const user = await getCurrentUser(); 21 | 22 | if (!user) { 23 | redirect(authOptions?.pages?.signIn || "/login"); 24 | } 25 | 26 | return ( 27 | 28 | 32 |
33 |
34 |

Keywords

35 | 36 |
37 |
38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /app/(project)/project/layout.tsx: -------------------------------------------------------------------------------- 1 | import { notFound } from "next/navigation"; 2 | 3 | import { dashboardConfig } from "@/config/dashboard"; 4 | import { getCurrentUser } from "@/lib/session"; 5 | import { DashboardNav, NestedNav } from "@/components/layout/nav"; 6 | import { NavBar } from "@/components/layout/navbar"; 7 | import { SiteFooter } from "@/components/layout/site-footer"; 8 | 9 | interface DashboardLayoutProps { 10 | children?: React.ReactNode; 11 | } 12 | 13 | export default async function DashboardLayout({ 14 | children, 15 | }: DashboardLayoutProps) { 16 | const user = await getCurrentUser(); 17 | 18 | if (!user) { 19 | return notFound(); 20 | } 21 | 22 | return ( 23 |
24 | 25 | 26 |
27 | 30 |
31 | {children} 32 |
33 |
34 | 35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /app/(project)/project/loading.tsx: -------------------------------------------------------------------------------- 1 | import { CardSkeleton } from "@/components/shared/card-skeleton" 2 | import { DashboardHeader } from "@/components/dashboard/header" 3 | import { DashboardShell } from "@/components/dashboard/shell" 4 | import { Button } from "@/components/ui/button" 5 | 6 | export default function DashboardLoading() { 7 | return ( 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 |
18 |
19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /app/(project)/project/playground/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next"; 2 | import { redirect } from "next/navigation"; 3 | import { SearchParams } from "@/types"; 4 | 5 | import { getCurrentUser } from "@/lib/session"; 6 | import { Card, CardContent } from "@/components/ui/card"; 7 | import { DashboardHeader } from "@/components/dashboard/header"; 8 | import { getCampaignById } from "@/app/actions/campaign"; 9 | 10 | import { VoiceTonePersonalityForm } from "./components/form"; 11 | 12 | export const metadata: Metadata = { 13 | title: "Playground", 14 | description: "Experiment real-time with keywords.", 15 | }; 16 | 17 | export default async function PlaygroundPage({ 18 | searchParams, 19 | }: { 20 | searchParams: SearchParams<"id">; 21 | }) { 22 | const user = await getCurrentUser(); 23 | if (!user?.id) { 24 | redirect("/login"); 25 | } 26 | 27 | const campaignId = searchParams.id as string; 28 | if (!campaignId) { 29 | redirect("/dashboard/campaigns"); 30 | } 31 | 32 | const result = await getCampaignById(campaignId); 33 | if (!result.data) { 34 | redirect("/dashboard/campaigns"); 35 | } 36 | 37 | return ( 38 | <> 39 | 43 | 44 | 45 | 51 | 52 | 53 | 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /app/(project)/project/preferences/loading.tsx: -------------------------------------------------------------------------------- 1 | import { DashboardHeader } from "@/components/dashboard/header"; 2 | import { DashboardShell } from "@/components/dashboard/shell"; 3 | import { CardSkeleton } from "@/components/shared/card-skeleton"; 4 | 5 | export default function DashboardBillingLoading() { 6 | return ( 7 | 8 | 12 |
13 | 14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /app/(project)/project/preferences/page.tsx: -------------------------------------------------------------------------------- 1 | import { SearchParams } from "@/types"; 2 | import { redirect } from "next/navigation"; 3 | 4 | import { getPostPreferencesByCampaign } from "@/app/actions/post-preferences"; 5 | import { AddPostPreferencesModal } from "@/components/dashboard/add-post-preferences-moda"; 6 | import { DashboardHeader } from "@/components/dashboard/header"; 7 | import { PostPreferencesForm } from "@/components/dashboard/post-preferences-form"; 8 | import { DashboardShell } from "@/components/dashboard/shell"; 9 | import { authOptions } from "@/lib/auth"; 10 | import { getCurrentUser } from "@/lib/session"; 11 | 12 | export const metadata = { 13 | title: "BuzzDaddy Autopilot", 14 | description: "Manage post frequency, target platforms and interaction types.", 15 | }; 16 | 17 | interface PreferencesPageProps { 18 | searchParams: SearchParams<"id">; 19 | } 20 | 21 | export default async function PreferencesPage({ 22 | searchParams, 23 | }: PreferencesPageProps) { 24 | const user = await getCurrentUser(); 25 | 26 | if (!user) { 27 | redirect(authOptions?.pages?.signIn || "/login"); 28 | } 29 | 30 | const preferences = await getPostPreferencesByCampaign( 31 | searchParams.id as string, 32 | ); 33 | 34 | if (!preferences.data) { 35 | return ; 36 | } 37 | 38 | return ( 39 | <> 40 | 41 | 45 |
46 | 47 |
48 |
49 | 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /app/(project)/project/settings/loading.tsx: -------------------------------------------------------------------------------- 1 | import { CardSkeleton } from "@/components/shared/card-skeleton" 2 | import { DashboardHeader } from "@/components/dashboard/header" 3 | import { DashboardShell } from "@/components/dashboard/shell" 4 | 5 | export default function DashboardSettingsLoading() { 6 | return ( 7 | 8 | 12 |
13 | 14 |
15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /app/(project)/project/settings/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation" 2 | 3 | import { authOptions } from "@/lib/auth" 4 | import { getCurrentUser } from "@/lib/session" 5 | import { DashboardHeader } from "@/components/dashboard/header" 6 | import { DashboardShell } from "@/components/dashboard/shell" 7 | import { UserNameForm } from "@/components/forms/user-name-form" 8 | import { InviteUserForm } from "@/components/forms/invite-user" 9 | 10 | export const metadata = { 11 | title: "Settings", 12 | description: "Manage account and website settings.", 13 | } 14 | 15 | export default async function SettingsPage() { 16 | const user = await getCurrentUser() 17 | 18 | if (!user) { 19 | redirect(authOptions?.pages?.signIn || "/login") 20 | } 21 | 22 | return ( 23 | 24 | 28 |
29 | 30 | 31 |
32 |
33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /app/actions/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ai"; 2 | export * from "./apify"; 3 | export * from "./campaign"; 4 | export * from "./keywords"; 5 | export * from "./notification"; 6 | export * from "./post-preferences"; 7 | export * from "./twitter"; 8 | -------------------------------------------------------------------------------- /app/actions/twitter.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { Message } from "@/types"; 4 | 5 | import { client } from "@/lib/twitter"; 6 | 7 | export const postComment = async ( 8 | tweetId: string, 9 | comment: string, 10 | ): Promise => { 11 | console.log("Function postComment started"); 12 | console.log(`Received tweetId: ${tweetId}, comment: ${comment}`); 13 | 14 | try { 15 | console.log("Initializing Twitter client"); 16 | const c = client(); 17 | 18 | console.log("Posting comment to Twitter"); 19 | const postComment = await c.v2.reply(comment, tweetId); 20 | 21 | console.log("POSTING COMMENT RESPONSE: ", postComment); 22 | 23 | if (postComment.errors) { 24 | console.error("Errors in posting comment: ", postComment.errors); 25 | return { 26 | type: "error", 27 | message: "Errors in posting comment", 28 | }; 29 | } 30 | 31 | console.log("Comment posted successfully, data: ", postComment.data); 32 | 33 | return { 34 | type: "success", 35 | message: "Comment posted successfully", 36 | }; 37 | } catch (error) { 38 | if (error instanceof Error) { 39 | console.error("Error caught in postComment: ", error); 40 | return { 41 | type: "error", 42 | message: "Error caught in postComment", 43 | }; 44 | } else { 45 | console.error("An unexpected error occurred in postComment"); 46 | return { 47 | type: "error", 48 | message: "An unexpected error occurred in postComment", 49 | }; 50 | } 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth" 2 | 3 | import { authOptions } from "@/lib/auth" 4 | 5 | const handler = NextAuth(authOptions) 6 | 7 | export { handler as GET, handler as POST } 8 | -------------------------------------------------------------------------------- /app/api/openai/openai.ts: -------------------------------------------------------------------------------- 1 | import OpenAI from "openai"; 2 | 3 | const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); 4 | 5 | export const callOpenAI = async (text: string): Promise => { 6 | const completion = await openai.chat.completions.create({ 7 | messages: [ 8 | { 9 | role: "system", 10 | content: `TASK: Craft a message as if you are traversing social media. You are an average American who happened to see this post and feel connected to it because you are a happy user of OUR STARTUP /PRODUCT NAME HERE 11 | INSTRUCTIONS: Upsell our startup / product organically, it should sound natural not an ad. Weave it in with a joke or review or state your connection, however it connects naturally to the subject matter of the post. No more than 3 sentences. Use placeholder "StartupNameHere" to replace with our startup / product name.`, 12 | }, 13 | { role: "user", content: text }, 14 | ], 15 | model: "gpt-4o", 16 | }); 17 | 18 | const messageContent = completion.choices[0]?.message.content ?? ""; 19 | console.log(messageContent); 20 | return messageContent; 21 | }; -------------------------------------------------------------------------------- /app/api/openai/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from "next/server"; 2 | import { callOpenAI } from "./openai"; 3 | 4 | export const runtime="edge"; 5 | 6 | export async function POST(req: NextRequest) { 7 | try { 8 | const body = await req.json(); 9 | const completion = await callOpenAI(body.text); 10 | return NextResponse.json({ result: completion }, { status: 200 }); 11 | } catch (error: unknown) { 12 | const message = 13 | error instanceof Error ? error.message : "Unknown error occurred"; 14 | return NextResponse.json({ error: message }, { status: 400 }); 15 | } 16 | } -------------------------------------------------------------------------------- /app/opengraph-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cameronking4/ReplyGuy-clone/391eefab4301d1cd71037714746fd43c98cca16a/app/opengraph-image.jpg -------------------------------------------------------------------------------- /app/robots.ts: -------------------------------------------------------------------------------- 1 | import { MetadataRoute } from "next" 2 | 3 | export default function robots(): MetadataRoute.Robots { 4 | return { 5 | rules: { 6 | userAgent: "*", 7 | allow: "/", 8 | }, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /assets/fonts/CalSans-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cameronking4/ReplyGuy-clone/391eefab4301d1cd71037714746fd43c98cca16a/assets/fonts/CalSans-SemiBold.ttf -------------------------------------------------------------------------------- /assets/fonts/CalSans-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cameronking4/ReplyGuy-clone/391eefab4301d1cd71037714746fd43c98cca16a/assets/fonts/CalSans-SemiBold.woff2 -------------------------------------------------------------------------------- /assets/fonts/Inter-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cameronking4/ReplyGuy-clone/391eefab4301d1cd71037714746fd43c98cca16a/assets/fonts/Inter-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cameronking4/ReplyGuy-clone/391eefab4301d1cd71037714746fd43c98cca16a/assets/fonts/Inter-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/index.ts: -------------------------------------------------------------------------------- 1 | import localFont from "next/font/local"; 2 | import { Inter as FontSans, Urbanist } from "next/font/google"; 3 | 4 | export const fontSans = FontSans({ 5 | subsets: ["latin"], 6 | variable: "--font-sans", 7 | }) 8 | 9 | export const fontUrban = Urbanist({ 10 | subsets: ["latin"], 11 | variable: "--font-urban", 12 | }) 13 | 14 | export const fontHeading = localFont({ 15 | src: "./CalSans-SemiBold.woff2", 16 | variable: "--font-heading", 17 | }) 18 | -------------------------------------------------------------------------------- /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": "styles/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /components/analytics.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { Analytics as VercelAnalytics } from "@vercel/analytics/react" 4 | 5 | export function Analytics() { 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /components/billing-info.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | 5 | import { buttonVariants } from "@/components/ui/button" 6 | import { 7 | Card, 8 | CardContent, 9 | CardDescription, 10 | CardFooter, 11 | CardHeader, 12 | CardTitle, 13 | } from "@/components/ui/card" 14 | import { cn, formatDate } from "@/lib/utils" 15 | import Link from "next/link" 16 | import { UserSubscriptionPlan } from "types" 17 | 18 | interface BillingInfoProps extends React.HTMLAttributes { 19 | subscriptionPlan: UserSubscriptionPlan; 20 | } 21 | 22 | export function BillingInfo({ 23 | subscriptionPlan 24 | }: BillingInfoProps) { 25 | 26 | return ( 27 | 28 | 29 | Subscription Plan 30 | 31 | You are currently on the {subscriptionPlan.title}{" "} 32 | plan. 33 | 34 | 35 | {subscriptionPlan.description} 36 | 37 | 41 | {subscriptionPlan.isPaid ? "Manage Subscription" : "Upgrade now"} 42 | 43 | 44 | {subscriptionPlan.isPaid ? ( 45 |

46 | {subscriptionPlan.isCanceled 47 | ? "Your plan will be canceled on " 48 | : "Your plan renews on "} 49 | {formatDate(subscriptionPlan.stripeCurrentPeriodEnd)}. 50 |

51 | ) : null} 52 |
53 |
54 | ) 55 | } 56 | -------------------------------------------------------------------------------- /components/browser.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/utils/cn' 2 | 3 | export const BrowserComponent: React.FC<{ children?: React.ReactNode; className?: string }> = ({ 4 | className, 5 | children 6 | }) => ( 7 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
28 | 39 | 40 | 41 | 42 | 43 | https://buzzdaddy.ai 44 | 45 |
46 |
47 |
48 |
{children}
49 |
50 | ) 51 | -------------------------------------------------------------------------------- /components/connect-linkedin-button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRouter } from "next/navigation"; 4 | import { LinkIcon } from "lucide-react"; 5 | import { toast } from "sonner"; 6 | 7 | import { Button } from "@/components/ui/button"; 8 | import { connectLinkedInAccount } from "@/app/(dashboard)/dashboard/actions"; 9 | 10 | export function ConnectLinkedInButton() { 11 | const router = useRouter(); 12 | 13 | const onSubmit = async () => { 14 | const result = await connectLinkedInAccount(); 15 | if (result.type === "error") { 16 | toast.error(result.message); 17 | } 18 | // else { 19 | // toast.success(result.message); 20 | // router.push(result.data); 21 | // } 22 | 23 | if (result.data) { 24 | toast.success(result.message); 25 | if (result.data) { 26 | router.push(result.data); 27 | } else { 28 | toast.error("Unexpected error: No URL returned."); 29 | } 30 | } 31 | }; 32 | 33 | return ( 34 | 43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /components/connect-linkedin-card.tsx: -------------------------------------------------------------------------------- 1 | import { LinkedinIcon } from "lucide-react"; 2 | 3 | import { ConnectLinkedInButton } from "@/components/connect-linkedin-button"; 4 | import { 5 | Card, 6 | CardContent, 7 | CardDescription, 8 | CardFooter, 9 | CardHeader, 10 | CardTitle, 11 | } from "@/components/ui/card"; 12 | 13 | export const ConnectLinkedInCard = () => { 14 | 15 | 16 | 17 | return ( 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Connect your LinkedIn account to share your posts. 27 | 28 | 29 | 30 | 31 | 32 | 33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /components/connect-twitter-button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRouter } from "next/navigation"; 4 | import { LinkIcon } from "lucide-react"; 5 | import { toast } from "sonner"; 6 | 7 | import { Button } from "@/components/ui/button"; 8 | import { connectTwitterAccount } from "@/app/(dashboard)/dashboard/actions"; 9 | 10 | export function ConnectTwitterButton() { 11 | const router = useRouter(); 12 | const onSubmit = async () => { 13 | const result = await connectTwitterAccount(); 14 | if (result.type === "error") { 15 | toast.error(result.message); 16 | } 17 | // else { 18 | // toast.success(result.message); 19 | // router.push(result.data); 20 | // } 21 | if (result.data) { 22 | toast.success(result.message); 23 | router.push(result.data); 24 | } 25 | }; 26 | 27 | return ( 28 | <> 29 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /components/connect-twitter-card.tsx: -------------------------------------------------------------------------------- 1 | import { FaXTwitter } from "react-icons/fa6"; 2 | 3 | import { ConnectTwitterButton } from "@/components/connect-twitter-button"; 4 | import { 5 | Card, 6 | CardContent, 7 | CardDescription, 8 | CardFooter, 9 | CardHeader, 10 | CardTitle, 11 | } from "@/components/ui/card"; 12 | 13 | export const ConnectTwitterCard = () => { 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Connect your X account to share your posts. 24 | 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /components/content/mdx-card.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | interface CardProps extends React.HTMLAttributes { 6 | href?: string 7 | disabled?: boolean 8 | } 9 | 10 | export function MdxCard({ 11 | href, 12 | className, 13 | children, 14 | disabled, 15 | ...props 16 | }: CardProps) { 17 | return ( 18 |
26 |
27 |
28 | {children} 29 |
30 |
31 | {href && ( 32 | 33 | View 34 | 35 | )} 36 |
37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /components/dashboard/activity-log/columns.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ActivityLog } from "@prisma/client"; 4 | import { ColumnDef } from "@tanstack/react-table"; 5 | import { ArrowUpDown } from "lucide-react"; 6 | 7 | import { Button } from "@/components/ui/button"; 8 | 9 | export const columns: ColumnDef[] = [ 10 | { 11 | accessorKey: "type", 12 | header: ({ column }) => { 13 | return ( 14 | 21 | ); 22 | }, 23 | }, 24 | { 25 | accessorKey: "message", 26 | header: "Message", 27 | }, 28 | { 29 | accessorKey: "createdAt", 30 | header: ({ column }) => { 31 | return ( 32 |
33 | 40 |
41 | ); 42 | }, 43 | 44 | cell: ({ row }) => { 45 | const date = row.original.createdAt; 46 | 47 | const formatDate = (date: string | Date): string => { 48 | const parsedDate = new Date(date); 49 | return parsedDate.toLocaleString(); // Adjust the format as needed 50 | }; 51 | const formattedDate = formatDate(date); 52 | 53 | return
{formattedDate}
; 54 | }, 55 | }, 56 | ]; 57 | -------------------------------------------------------------------------------- /components/dashboard/activity-log/index.tsx: -------------------------------------------------------------------------------- 1 | import { ActivityLog as TActivityLog } from "@prisma/client"; 2 | 3 | import { getActivityLog } from "@/app/actions/activity-log"; 4 | 5 | import { columns } from "./columns"; 6 | import { DataTable } from "./data-table"; 7 | 8 | export const ActivityLog = async () => { 9 | const result = await getActivityLog(); 10 | 11 | let data: TActivityLog[] = []; 12 | 13 | if (result.type === "error" || !result.data) { 14 | data = []; 15 | } else if (result.data) { 16 | data = result.data; 17 | } 18 | 19 | return ( 20 |
21 | {/*

22 | Activity Log 23 |

*/} 24 | 25 |
26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /components/dashboard/add-keywords-modal.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useKeywordsModal } from "@/hooks/use-keywords-modal"; 4 | import { 5 | Dialog, 6 | DialogContent, 7 | DialogDescription, 8 | DialogHeader, 9 | DialogTitle, 10 | } from "@/components/ui/dialog"; 11 | 12 | import { KeywordSelectForm } from "./keyword-select-form"; 13 | 14 | export function AddKeywordsModal() { 15 | const { isOpen, onChange, campaignId, onClose } = useKeywordsModal(); 16 | 17 | return ( 18 | 19 | 20 | 21 | Keyword Preferences Configuration 22 | 23 | Add keywords for your campaign to target specific audiences and 24 | finding relevant posts and comments. 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /components/dashboard/empty-campaign-card.tsx: -------------------------------------------------------------------------------- 1 | import { Card, CardContent } from "@/components/ui/card"; 2 | 3 | interface CampaignProps { 4 | children: React.ReactNode; 5 | } 6 | 7 | export function EmptyCampaignCard({ children }: CampaignProps) { 8 | return ( 9 |
10 | 11 | 12 | {children} 13 | 14 | 15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /components/dashboard/header.tsx: -------------------------------------------------------------------------------- 1 | interface DashboardHeaderProps { 2 | heading: string 3 | text?: string 4 | children?: React.ReactNode 5 | } 6 | 7 | export function DashboardHeader({ 8 | heading, 9 | text, 10 | children, 11 | }: DashboardHeaderProps) { 12 | return ( 13 |
14 |
15 |

{heading}

16 | {text &&

{text}

} 17 |
18 | {children} 19 |
20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /components/dashboard/shell.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | interface DashboardShellProps extends React.HTMLAttributes {} 6 | 7 | export function DashboardShell({ 8 | children, 9 | className, 10 | ...props 11 | }: DashboardShellProps) { 12 | return ( 13 |
14 | {children} 15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /components/disconnect-linkedin-button.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useRouter } from "next/navigation"; 4 | import { UnlinkIcon } from "lucide-react"; 5 | import { useSession } from "next-auth/react"; 6 | import { toast } from "sonner"; 7 | 8 | import { Button } from "@/components/ui/button"; 9 | import { deleteLinkedin } from "@/app/(dashboard)/dashboard/actions"; 10 | 11 | export const DisconnectLinkedInAccount = () => { 12 | const { data: session, status } = useSession(); 13 | const router = useRouter(); 14 | 15 | const handleClick = async () => { 16 | try { 17 | if (status === "unauthenticated" || !session?.user) { 18 | toast.error("You are not logged in"); 19 | return; 20 | } 21 | const result = await deleteLinkedin(session.user.id); 22 | console.log(result); 23 | if (result.status === "success") { 24 | toast.success(result.message); 25 | router.refresh(); 26 | } else if (result.status === "error") { 27 | toast.error(result.message); 28 | } 29 | } catch (error) { 30 | console.error("Error disconnecting LinkedIn account:", error); 31 | toast.error("Failed to disconnect LinkedIn account"); 32 | } 33 | }; 34 | 35 | return ( 36 | 43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /components/docs/page-header.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | interface DocsPageHeaderProps extends React.HTMLAttributes { 4 | heading: string 5 | text?: string 6 | } 7 | 8 | export function DocsPageHeader({ 9 | heading, 10 | text, 11 | className, 12 | ...props 13 | }: DocsPageHeaderProps) { 14 | return ( 15 | <> 16 |
17 |

18 | {heading} 19 |

20 | {text &&

{text}

} 21 |
22 |
23 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /components/docs/search.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | 5 | import { cn } from "@/lib/utils" 6 | import { Input } from "@/components/ui/input" 7 | import { toast } from "@/components/ui/use-toast" 8 | 9 | interface DocsSearchProps extends React.HTMLAttributes {} 10 | 11 | export function DocsSearch({ className, ...props }: DocsSearchProps) { 12 | function onSubmit(event: React.SyntheticEvent) { 13 | event.preventDefault() 14 | 15 | return toast({ 16 | title: "Not implemented", 17 | description: "We're still working on the search.", 18 | }) 19 | } 20 | 21 | return ( 22 |
27 | 32 | 33 | K 34 | 35 |
36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /components/docs/sidebar-nav.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import Link from "next/link" 4 | import { usePathname } from "next/navigation" 5 | 6 | import { SidebarNavItem } from "types" 7 | import { cn } from "@/lib/utils" 8 | 9 | export interface DocsSidebarNavProps { 10 | items: SidebarNavItem[] 11 | } 12 | 13 | export function DocsSidebarNav({ items }: DocsSidebarNavProps) { 14 | const pathname = usePathname() 15 | 16 | return items.length ? ( 17 |
18 | {items.map((item) => ( 19 |
20 |

21 | {item.title} 22 |

23 | {item.items ? ( 24 | 25 | ) : null} 26 |
27 | ))} 28 |
29 | ) : null 30 | } 31 | 32 | interface DocsSidebarNavItemsProps { 33 | items: SidebarNavItem[] 34 | pathname: string | null 35 | } 36 | 37 | export function DocsSidebarNavItems({ 38 | items, 39 | pathname, 40 | }: DocsSidebarNavItemsProps) { 41 | return items?.length ? ( 42 |
43 | {items.map((item, index) => 44 | !item.disabled && item.href ? ( 45 | 57 | {item.title} 58 | 59 | ) : ( 60 | 64 | {item.title} 65 | 66 | ) 67 | )} 68 |
69 | ) : null 70 | } 71 | -------------------------------------------------------------------------------- /components/forms/billing-form-button.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { generateUserStripe } from '@/actions/generate-user-stripe' 4 | import { Icons } from "@/components/shared/icons" 5 | import { Button } from "@/components/ui/button" 6 | import { SubscriptionPlan, UserSubscriptionPlan } from "@/types" 7 | import { useTransition } from 'react' 8 | 9 | interface BillingFormButtonProps { 10 | offer: SubscriptionPlan; 11 | subscriptionPlan: UserSubscriptionPlan; 12 | year: boolean; 13 | } 14 | 15 | export function BillingFormButton({ year, offer, subscriptionPlan }: BillingFormButtonProps) { 16 | let [isPending, startTransition] = useTransition(); 17 | const generateUserStripeSession = generateUserStripe.bind( 18 | null, 19 | offer.stripeIds[year ? "yearly" : "monthly"] 20 | ); 21 | 22 | const stripeSessionAction = () => startTransition(async () => await generateUserStripeSession()); 23 | 24 | return ( 25 | 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /components/gif-card.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { cn } from "@/lib/utils"; 3 | import { Badge } from "@/components/ui/badge"; 4 | 5 | export interface GifCardProps { 6 | url: string; 7 | title: string; 8 | description: string; 9 | } 10 | 11 | export function GifCard({ url, title, description }: GifCardProps) { 12 | return ( 13 |
14 |
25 |
26 | 27 | {description} 28 | 29 |

30 | {title} 31 |

32 |
33 |
34 |
35 | ); 36 | } 37 | 38 | export const GifGrid = ({ gifs }) => ( 39 |
40 |
41 | {gifs.map((gif, index) => ( 42 | 43 | ))} 44 |
45 |
46 | ); -------------------------------------------------------------------------------- /components/layout/mobile-nav.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import Link from "next/link" 3 | 4 | import { MainNavItem } from "types" 5 | import { siteConfig } from "@/config/site" 6 | import { cn } from "@/lib/utils" 7 | import { useLockBody } from "@/hooks/use-lock-body" 8 | import { Icons } from "@/components/shared/icons" 9 | 10 | interface MobileNavProps { 11 | items: MainNavItem[] 12 | children?: React.ReactNode 13 | } 14 | 15 | export function MobileNav({ items, children }: MobileNavProps) { 16 | useLockBody() 17 | 18 | return ( 19 |
24 |
25 | 26 | 27 | {siteConfig.name} 28 | 29 | 43 | {children} 44 |
45 |
46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /components/layout/mode-toggle.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { useTheme } from "next-themes" 5 | 6 | import { Button } from "@/components/ui/button" 7 | import { 8 | DropdownMenu, 9 | DropdownMenuContent, 10 | DropdownMenuItem, 11 | DropdownMenuTrigger, 12 | } from "@/components/ui/dropdown-menu" 13 | import { Icons } from "@/components/shared/icons" 14 | 15 | export function ModeToggle() { 16 | const { setTheme } = useTheme() 17 | 18 | return ( 19 | 20 | 21 | 26 | 27 | 28 | setTheme("light")}> 29 | 30 | Light 31 | 32 | setTheme("dark")}> 33 | 34 | Dark 35 | 36 | setTheme("system")}> 37 | 38 | System 39 | 40 | 41 | 42 | ) 43 | } 44 | -------------------------------------------------------------------------------- /components/layout/navbar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import useScroll from "@/hooks/use-scroll"; 4 | import { MainNavItem } from "@/types"; 5 | import { User } from "next-auth"; 6 | import { MainNav } from "./main-nav"; 7 | import { UserAccountNav } from "./user-account-nav"; 8 | import { Button, buttonVariants } from "@/components/ui/button"; 9 | import Link from "next/link"; 10 | import { cn } from "@/lib/utils"; 11 | import { useSigninModal } from "@/hooks/use-signin-modal"; 12 | 13 | 14 | interface NavBarProps { 15 | user: Pick | undefined 16 | items?: MainNavItem[] 17 | children?: React.ReactNode 18 | rightElements?: React.ReactNode 19 | scroll?: boolean 20 | } 21 | 22 | export function NavBar({ user, items, children, rightElements, scroll = false }: NavBarProps) { 23 | const scrolled = useScroll(50); 24 | const signInModal = useSigninModal(); 25 | 26 | return ( 27 |
33 |
34 | {children} 35 | 36 |
37 | {rightElements} 38 | 39 | {!user ? ( 40 | 46 | Get started 47 | 48 | ) : null} 49 | 50 | {user ? ( 51 | 52 | ) : ( 53 | 54 | )} 55 |
56 |
57 |
58 | ); 59 | } -------------------------------------------------------------------------------- /components/layout/preview-select.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button" 2 | import { 3 | DropdownMenu, 4 | DropdownMenuContent, 5 | DropdownMenuGroup, 6 | DropdownMenuItem, 7 | DropdownMenuLabel, 8 | DropdownMenuPortal, 9 | DropdownMenuSeparator, 10 | DropdownMenuShortcut, 11 | DropdownMenuSub, 12 | DropdownMenuSubContent, 13 | DropdownMenuSubTrigger, 14 | DropdownMenuTrigger, 15 | } from "@/components/ui/dropdown-menu" 16 | 17 | export function PreviewDropdown() { 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | Project links 25 | 26 | 27 | 28 | qashboard.com 29 | 30 | 31 | 32 | https://turbo-computing-machine-pgp4rxpr669hrw-3000.app.github.dev/ 33 | 34 | 35 | 36 | 37 | 38 | ) 39 | } -------------------------------------------------------------------------------- /components/layout/select-worktype.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | import * as React from "react" 3 | import { CaretSortIcon } from "@radix-ui/react-icons" 4 | import { ShieldAlertIcon, BellIcon } from "lucide-react"; 5 | import { Button } from "@/components/ui/button" 6 | import { 7 | Select, 8 | SelectContent, 9 | SelectGroup, 10 | SelectItem, 11 | SelectLabel, 12 | SelectTrigger, 13 | SelectValue, 14 | } from "@/components/ui/select" 15 | 16 | export function CreateDropdownMenu() { 17 | const [isOpen, setIsOpen] = React.useState(true) 18 | 19 | return ( 20 | 36 | ) 37 | } -------------------------------------------------------------------------------- /components/layout/site-footer.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { Feedback } from "@/components/feedback"; 4 | import { cn } from "@/lib/utils" 5 | import { Icons } from "@/components/shared/icons" 6 | import { ModeToggle } from "@/components/layout/mode-toggle" 7 | 8 | export function SiteFooter({ className }: React.HTMLAttributes) { 9 | return ( 10 |
11 |
12 |
13 | 14 |

15 | All Rights Reserved{" "} 16 | 22 | BuzzDaddy 23 | 24 | {" "}| Copyright 2024 25 |

26 |
27 |
28 | 29 | 30 |
31 |
32 |
33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /components/layout/sticky-scroll.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import { StickyScroll } from "../ui/sticky-scroll-reveal"; 4 | 5 | const content = [ 6 | { 7 | title: "Collaborative Editing", 8 | description: 9 | "Work together in real time with your team, clients, and stakeholders. Collaborate on documents, share ideas, and make decisions quickly. With our platform, you can streamline your workflow and increase productivity.", 10 | }, 11 | { 12 | title: "Real time changes", 13 | description: 14 | "See changes as they happen. With our platform, you can track every modification in real time. No more confusion about the latest version of your project. Say goodbye to the chaos of version control and embrace the simplicity of real-time updates.", 15 | }, 16 | { 17 | title: "Version control", 18 | description: 19 | "Experience real-time updates and never stress about version control again. Our platform ensures that you're always working on the most recent version of your project, eliminating the need for constant manual updates. Stay in the loop, keep your team aligned, and maintain the flow of your work without any interruptions.", 20 | }, 21 | { 22 | title: "Running out of content", 23 | description: 24 | "Experience real-time updates and never stress about version control again. Our platform ensures that you're always working on the most recent version of your project, eliminating the need for constant manual updates. Stay in the loop, keep your team aligned, and maintain the flow of your work without any interruptions.", 25 | }, 26 | ]; 27 | export function StickyScrollRevealDemo() { 28 | return ( 29 |
30 | 31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /components/modal-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { SignInModal } from "@/components/layout/sign-in-modal"; 4 | import { useMounted } from "@/hooks/use-mounted"; 5 | 6 | export const ModalProvider = () => { 7 | const mounted = useMounted() 8 | 9 | if (!mounted) { 10 | return null; 11 | } 12 | 13 | return ( 14 | <> 15 | 16 | {/* add your own modals here... */} 17 | 18 | ); 19 | }; -------------------------------------------------------------------------------- /components/providers.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { SessionProvider as NextAuthSessionProvider } from "next-auth/react"; 4 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 5 | import { ThemeProviderProps } from "next-themes/dist/types"; 6 | 7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 8 | return {children}; 9 | } 10 | 11 | export function SessionProvider({ children }: { children: React.ReactNode }) { 12 | return {children}; 13 | } 14 | -------------------------------------------------------------------------------- /components/query-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 4 | 5 | const client = new QueryClient(); 6 | 7 | export function QueryProvider({ children }: { children: React.ReactNode }) { 8 | return {children}; 9 | } 10 | -------------------------------------------------------------------------------- /components/radar/IconContainer.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | 4 | import { twMerge } from "tailwind-merge"; 5 | import { HiDocumentText } from "react-icons/hi"; 6 | import { motion } from "framer-motion"; 7 | 8 | export const IconContainer = ({ icon, text, delay }: any) => { 9 | return ( 10 | 27 |
28 | {icon || } 29 |
30 |
31 |
32 | {text || `Web Development`} 33 |
34 |
35 |
36 | ); 37 | }; -------------------------------------------------------------------------------- /components/radar/Preview.tsx: -------------------------------------------------------------------------------- 1 | import { IconContainer } from "./IconContainer"; 2 | import { FaTwitter, FaLinkedin, FaReddit } from "react-icons/fa"; 3 | import { Radar } from "./Radar"; 4 | 5 | export const PreviewRadar = () => { 6 | return ( 7 |
8 |
9 |
10 | } 14 | /> 15 |
16 |
17 |
18 |
19 | } 23 | /> 24 | } 28 | /> 29 |
30 |
31 |
32 |
33 | } 37 | /> 38 | } 42 | /> 43 |
44 |
45 | 46 | 47 |
48 |
49 | ); 50 | }; 51 | 52 | export default PreviewRadar; 53 | -------------------------------------------------------------------------------- /components/radar/Radar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import { twMerge } from "tailwind-merge"; 4 | import { motion } from "framer-motion"; 5 | 6 | export const Radar = ({ className }: any) => { 7 | const circles = new Array(8).fill(1); 8 | return ( 9 |
15 |
22 | {/* Radar line that rotates */} 23 |
24 |
25 | {/* concentric circles */} 26 | {circles.map((circle, idx) => ( 27 | 36 | ))} 37 |
38 | ); 39 | }; 40 | 41 | {/* Creating circles */} 42 | export const Circle = ({ className, children, idx, ...rest }: any) => { 43 | return ( 44 | 61 | ); 62 | }; 63 | 64 | -------------------------------------------------------------------------------- /components/shared/callout.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | interface CalloutProps { 4 | icon?: string 5 | children?: React.ReactNode 6 | type?: "default" | "warning" | "danger" | "info" 7 | } 8 | 9 | // ✅💡⚠️🚫🚨 10 | export function Callout({ 11 | children, 12 | icon, 13 | type = "default", 14 | ...props 15 | }: CalloutProps) { 16 | return ( 17 |
28 | {icon && {icon}} 29 |
{children}
30 |
31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /components/shared/card-skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card" 2 | import { Skeleton } from "@/components/ui/skeleton" 3 | 4 | export function CardSkeleton() { 5 | return ( 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /components/shared/header-section.tsx: -------------------------------------------------------------------------------- 1 | interface HeaderSectionProps { 2 | label?: string; 3 | title: string; 4 | subtitle?: string; 5 | } 6 | 7 | export function HeaderSection({ label, title, subtitle }: HeaderSectionProps) { 8 | return ( 9 |
10 | {label ? ( 11 |
12 | {label} 13 |
14 | ) : null} 15 |

16 | {title} 17 |

18 | {subtitle ? ( 19 |

20 | {subtitle} 21 |

22 | ) : null} 23 |
24 | ); 25 | } -------------------------------------------------------------------------------- /components/shared/modal.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import useMediaQuery from "@/hooks/use-media-query"; 4 | import { cn } from "@/lib/utils"; 5 | import { Drawer } from "vaul"; 6 | 7 | import { 8 | Dialog, 9 | DialogContent, 10 | } from "@/components/ui/dialog"; 11 | 12 | interface ModalProps { 13 | children: React.ReactNode; 14 | className?: string; 15 | showModal: boolean; 16 | setShowModal: () => void; 17 | } 18 | 19 | export function Modal({children, className, showModal, setShowModal}: ModalProps) { 20 | const { isMobile } = useMediaQuery(); 21 | 22 | if (isMobile) { 23 | return ( 24 | 25 | 26 | 27 | 33 |
34 |
35 |
36 | {children} 37 | 38 | 39 | 40 | 41 | ); 42 | } 43 | return ( 44 | 45 | 46 | {children} 47 | 48 | 49 | 50 | ); 51 | } 52 | -------------------------------------------------------------------------------- /components/shared/user-avatar.tsx: -------------------------------------------------------------------------------- 1 | import { User } from "@prisma/client" 2 | import { AvatarProps } from "@radix-ui/react-avatar" 3 | 4 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" 5 | import { Icons } from "@/components/shared/icons" 6 | 7 | interface UserAvatarProps extends AvatarProps { 8 | user: Pick 9 | } 10 | 11 | export function UserAvatar({ user, ...props }: UserAvatarProps) { 12 | return ( 13 | 14 | {user.image ? ( 15 | 16 | ) : ( 17 | 18 | {user.name} 19 | 20 | 21 | )} 22 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /components/tailwind-indicator.tsx: -------------------------------------------------------------------------------- 1 | export function TailwindIndicator() { 2 | if (process.env.NODE_ENV === "production") return null 3 | 4 | return ( 5 |
6 |
xs
7 |
sm
8 |
md
9 |
lg
10 |
xl
11 |
2xl
12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /components/ugc-text.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { motion } from "framer-motion"; 3 | import { HeroHighlight, Highlight } from "./ui/hero-highlight"; 4 | import SparklesText from "./magicui/sparkles-text"; 5 | 6 | export function UGCText() { 7 | return ( 8 | 9 | 24 | 25 | 26 | {" "} 27 | generate{" "} 28 | 29 | 10x more leads 30 | than traditional advertisements. 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /components/ui/accordion.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AccordionPrimitive from "@radix-ui/react-accordion" 5 | import { ChevronDown } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Accordion = AccordionPrimitive.Root 10 | 11 | const AccordionItem = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 20 | )) 21 | AccordionItem.displayName = "AccordionItem" 22 | 23 | const AccordionTrigger = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, children, ...props }, ref) => ( 27 | 28 | svg]:rotate-180", 32 | className 33 | )} 34 | {...props} 35 | > 36 | {children} 37 | 38 | 39 | 40 | )) 41 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName 42 | 43 | const AccordionContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, children, ...props }, ref) => ( 47 | 55 |
{children}
56 |
57 | )) 58 | AccordionContent.displayName = AccordionPrimitive.Content.displayName 59 | 60 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } 61 | -------------------------------------------------------------------------------- /components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { VariantProps, cva } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const alertVariants = cva( 7 | "relative w-full rounded-lg border p-4 [&>svg]:absolute [&>svg]:text-foreground [&>svg]:left-4 [&>svg]:top-4 [&>svg+div]:translate-y-[-3px] [&:has(svg)]:pl-11", 8 | { 9 | variants: { 10 | variant: { 11 | default: "bg-background text-foreground", 12 | destructive: 13 | "text-destructive border-destructive/50 dark:border-destructive [&>svg]:text-destructive text-destructive", 14 | }, 15 | }, 16 | defaultVariants: { 17 | variant: "default", 18 | }, 19 | } 20 | ) 21 | 22 | const Alert = React.forwardRef< 23 | HTMLDivElement, 24 | React.HTMLAttributes & VariantProps 25 | >(({ className, variant, ...props }, ref) => ( 26 |
32 | )) 33 | Alert.displayName = "Alert" 34 | 35 | const AlertTitle = React.forwardRef< 36 | HTMLParagraphElement, 37 | React.HTMLAttributes 38 | >(({ className, ...props }, ref) => ( 39 |
44 | )) 45 | AlertTitle.displayName = "AlertTitle" 46 | 47 | const AlertDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |
56 | )) 57 | AlertDescription.displayName = "AlertDescription" 58 | 59 | export { Alert, AlertTitle, AlertDescription } 60 | -------------------------------------------------------------------------------- /components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" 4 | 5 | const AspectRatio = AspectRatioPrimitive.Root 6 | 7 | export { AspectRatio } 8 | -------------------------------------------------------------------------------- /components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Avatar = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )) 21 | Avatar.displayName = AvatarPrimitive.Root.displayName 22 | 23 | const AvatarImage = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 32 | )) 33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 34 | 35 | const AvatarFallback = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | )) 48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 49 | 50 | export { Avatar, AvatarImage, AvatarFallback } 51 | -------------------------------------------------------------------------------- /components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { VariantProps, cva } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center border rounded-full 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 | "bg-primary hover:bg-primary/80 border-transparent text-primary-foreground", 13 | secondary: 14 | "bg-secondary hover:bg-secondary/80 border-transparent text-secondary-foreground", 15 | destructive: 16 | "bg-destructive hover:bg-destructive/80 border-transparent text-destructive-foreground", 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 | -------------------------------------------------------------------------------- /components/ui/bento-grid.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/utils/cn"; 2 | 3 | export const BentoGrid = ({ 4 | className, 5 | children, 6 | }: { 7 | className?: string; 8 | children?: React.ReactNode; 9 | }) => { 10 | return ( 11 |
17 | {children} 18 |
19 | ); 20 | }; 21 | 22 | export const BentoGridItem = ({ 23 | className, 24 | title, 25 | description, 26 | header, 27 | icon, 28 | }: { 29 | className?: string; 30 | title?: string | React.ReactNode; 31 | description?: string | React.ReactNode; 32 | header?: React.ReactNode; 33 | icon?: React.ReactNode; 34 | }) => { 35 | return ( 36 |
42 | {header} 43 |
44 | {icon} 45 |
46 | {title} 47 |
48 |
49 | {description} 50 |
51 |
52 |
53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { cva, VariantProps } from "class-variance-authority"; 3 | 4 | import { cn } from "@/lib/utils"; 5 | 6 | const buttonVariants = cva( 7 | "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background", 8 | { 9 | variants: { 10 | variant: { 11 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 12 | destructive: 13 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 14 | outline: 15 | "border border-input hover:bg-accent hover:text-accent-foreground", 16 | secondary: 17 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 18 | ghost: "hover:bg-accent hover:text-accent-foreground", 19 | link: "underline-offset-4 hover:underline text-primary", 20 | }, 21 | size: { 22 | default: "h-10 py-2 px-4", 23 | sm: "h-9 px-3 rounded-md", 24 | lg: "h-11 px-8 rounded-md", 25 | icon: "h-10 w-10 rounded-md", 26 | }, 27 | }, 28 | defaultVariants: { 29 | variant: "default", 30 | size: "default", 31 | }, 32 | }, 33 | ); 34 | 35 | export interface ButtonProps 36 | extends React.ButtonHTMLAttributes, 37 | VariantProps {} 38 | 39 | const Button = React.forwardRef( 40 | ({ className, variant, size, ...props }, ref) => { 41 | return ( 42 | 61 | ); 62 | }; 63 | 64 | export { StepButtonContainer }; 65 | -------------------------------------------------------------------------------- /components/ui/stepper/step.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { HorizontalStep } from "./horizontal-step"; 4 | import type { StepProps } from "./types"; 5 | import { useStepper } from "./use-stepper"; 6 | import { VerticalStep } from "./vertical-step"; 7 | 8 | // Props which shouldn't be passed to to the Step component from the user 9 | interface StepInternalConfig { 10 | index: number; 11 | isCompletedStep?: boolean; 12 | isCurrentStep?: boolean; 13 | isLastStep?: boolean; 14 | } 15 | 16 | interface FullStepProps extends StepProps, StepInternalConfig {} 17 | 18 | const Step = React.forwardRef( 19 | (props, ref: React.Ref) => { 20 | const { 21 | children, 22 | description, 23 | icon, 24 | state, 25 | checkIcon, 26 | errorIcon, 27 | index, 28 | isCompletedStep, 29 | isCurrentStep, 30 | isLastStep, 31 | isKeepError, 32 | label, 33 | onClickStep, 34 | } = props as FullStepProps; 35 | 36 | const { isVertical, isError, isLoading, clickable } = useStepper(); 37 | 38 | const hasVisited = isCurrentStep || isCompletedStep; 39 | 40 | const sharedProps = { 41 | isLastStep, 42 | isCompletedStep, 43 | isCurrentStep, 44 | index, 45 | isError, 46 | isLoading, 47 | clickable, 48 | label, 49 | description, 50 | hasVisited, 51 | icon, 52 | isKeepError, 53 | checkIcon, 54 | state, 55 | errorIcon, 56 | onClickStep, 57 | }; 58 | 59 | const renderStep = () => { 60 | switch (isVertical) { 61 | case true: 62 | return ( 63 | 64 | {children} 65 | 66 | ); 67 | default: 68 | return ; 69 | } 70 | }; 71 | 72 | return renderStep(); 73 | }, 74 | ); 75 | 76 | Step.displayName = "Step"; 77 | 78 | export { Step }; 79 | -------------------------------------------------------------------------------- /components/ui/stepper/use-media-query.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | export function useMediaQuery(query: string) { 4 | const [value, setValue] = React.useState(false); 5 | 6 | React.useEffect(() => { 7 | function onChange(event: MediaQueryListEvent) { 8 | setValue(event.matches); 9 | } 10 | 11 | const result = matchMedia(query); 12 | result.addEventListener("change", onChange); 13 | setValue(result.matches); 14 | 15 | return () => result.removeEventListener("change", onChange); 16 | }, [query]); 17 | 18 | return value; 19 | } 20 | -------------------------------------------------------------------------------- /components/ui/stepper/use-stepper.ts: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { StepperContext } from "./context"; 4 | 5 | function usePrevious(value: T): T | undefined { 6 | const ref = React.useRef(); 7 | 8 | React.useEffect(() => { 9 | ref.current = value; 10 | }, [value]); 11 | 12 | return ref.current; 13 | } 14 | 15 | export function useStepper() { 16 | const context = React.useContext(StepperContext); 17 | 18 | if (context === undefined) { 19 | throw new Error("useStepper must be used within a StepperProvider"); 20 | } 21 | 22 | const { children, className, ...rest } = context; 23 | 24 | const isLastStep = context.activeStep === context.steps.length - 1; 25 | const hasCompletedAllSteps = context.activeStep === context.steps.length; 26 | 27 | const previousActiveStep = usePrevious(context.activeStep); 28 | 29 | const currentStep = context.steps[context.activeStep]; 30 | const isOptionalStep = !!currentStep?.optional; 31 | 32 | const isDisabledStep = context.activeStep === 0; 33 | 34 | return { 35 | ...rest, 36 | isLastStep, 37 | hasCompletedAllSteps, 38 | isOptionalStep, 39 | isDisabledStep, 40 | currentStep, 41 | previousActiveStep, 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SwitchPrimitives from "@radix-ui/react-switch" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Switch = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | 25 | 26 | )) 27 | Switch.displayName = SwitchPrimitives.Root.displayName 28 | 29 | export { Switch } 30 | -------------------------------------------------------------------------------- /components/ui/tabs.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import * as TabsPrimitive from "@radix-ui/react-tabs"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | 8 | const Tabs = TabsPrimitive.Root; 9 | 10 | const TabsList = React.forwardRef< 11 | React.ElementRef, 12 | React.ComponentPropsWithoutRef 13 | >(({ className, ...props }, ref) => ( 14 | 22 | )); 23 | TabsList.displayName = TabsPrimitive.List.displayName; 24 | 25 | const TabsTrigger = React.forwardRef< 26 | React.ElementRef, 27 | React.ComponentPropsWithoutRef 28 | >(({ className, ...props }, ref) => ( 29 | 37 | )); 38 | TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; 39 | 40 | const TabsContent = React.forwardRef< 41 | React.ElementRef, 42 | React.ComponentPropsWithoutRef 43 | >(({ className, ...props }, ref) => ( 44 | 52 | )); 53 | TabsContent.displayName = TabsPrimitive.Content.displayName; 54 | 55 | export { Tabs, TabsContent, TabsList, TabsTrigger }; 56 | -------------------------------------------------------------------------------- /components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 |