Çiçekçi Asistanı Docs
Çiçekçi Asistanı Docs
HomeAnother Page
Folder
SubscriptionsPlan-Based Rendering
Payments

Plan-Based Rendering

Gate premium features, enforce quotas, and create upgrade prompts based on user subscriptions.

Plan-Based Rendering

Show different features to different plans! Gate premium features, enforce quotas, and create upgrade prompts based on user subscriptions.

Feature Gating Made Easy

Use the useCurrentPlan hook in client components, or withOrganizationAuthRequired for API routes.

Video Walkthrough

Client-Side: useCurrentPlan Hook

Access user plan data in any client component (organization context):

'use client'
import useCurrentPlan from '@/lib/users/useCurrentPlan'
import { Button } from '@/components/ui/button'
import { CreditCard, Crown } from 'lucide-react'
import Link from 'next/link'

function Dashboard() {
  const { currentPlan, isLoading } = useCurrentPlan()

  if (isLoading) return <div>Loading...</div>

  return (
    <div className="space-y-6">
      <div className="flex items-center justify-between">
        <h1 className="text-2xl font-bold">Dashboard</h1>
        <div className="badge">
          {currentPlan ? (
            <>
              <Crown className="w-4 h-4" />
              {currentPlan.name}
            </>
          ) : (
            'Free Plan'
          )}
        </div>
      </div>

      {currentPlan ? (
        <Link href="/app/billing">
          <Button variant="outline">
            <CreditCard className="w-4 h-4 mr-2" />
            Manage Subscription
          </Button>
        </Link>
      ) : (
        <div className="p-4 border rounded-lg bg-blue-50">
          <p className="mb-2">Unlock premium features!</p>
          <Link href="/pricing">
            <Button>Upgrade Now</Button>
          </Link>
        </div>
      )}
    </div>
  )
}

Hook Returns

useCurrentPlan provides currentPlan (plan object with quotas), isLoading, and error. It uses useOrganization under the hood.

Configure Plan Quotas

Define quotas in src/db/schema/plans.ts:

export const quotaSchema = z.object({
  canUseApp: z.boolean().default(true),
  numberOfThings: z.number(),
  somethingElse: z.string(),
  teamMembers: z.number().default(1),
})

Set quota values for each plan in /super-admin/plans.

Enforce Quotas in Components

'use client'
import useCurrentPlan from '@/lib/users/useCurrentPlan'
import Link from 'next/link'
import { Button } from '@/components/ui/button'
import { Lock } from 'lucide-react'

function AdvancedFeature() {
  const { currentPlan } = useCurrentPlan()

  // Check boolean quota
  if (!currentPlan?.quotas?.canUseApp) {
    return (
      <div className="p-6 border rounded-lg text-center">
        <Lock className="w-12 h-12 mx-auto mb-4 text-muted-foreground" />
        <h3 className="font-semibold mb-2">Premium Feature</h3>
        <p className="text-muted-foreground mb-4">
          Upgrade to Pro to access this feature
        </p>
        <Link href="/pricing">
          <Button>View Plans</Button>
        </Link>
      </div>
    )
  }

  return <div>{/* Premium feature content */}</div>
}

// Numerical quota check
function ProjectsList({ userProjects }) {
  const { currentPlan } = useCurrentPlan()
  const projectLimit = currentPlan?.quotas?.numberOfThings ?? 3
  const canCreateMore = userProjects.length < projectLimit

  return (
    <div>
      <h2>Projects ({userProjects.length}/{projectLimit})</h2>
      {canCreateMore ? (
        <Button>Create Project</Button>
      ) : (
        <Link href="/pricing">
          <Button variant="outline" size="sm">Upgrade</Button>
        </Link>
      )}
    </div>
  )
}

Best Practices

1. Graceful Degradation - Show clear message with upgrade path instead of hiding.

2. Show Limits Proactively - Display usage before users hit the limit.

3. Consistent UI - Keep layout same; show locked features with lock icon.

4. Always Check Server-Side - Never rely only on client-side checks for security.

Server-Side: API Routes

For organization-scoped API routes, use withOrganizationAuthRequired:

// src/app/api/app/some-feature/route.ts
import withOrganizationAuthRequired from '@/lib/auth/withOrganizationAuthRequired'
import { OrganizationRole } from '@/db/schema/organization'
import { NextResponse } from 'next/server'

export const POST = withOrganizationAuthRequired(
  async (req, context) => {
    const organization = await context.session.organization
    const currentPlan = organization.plan

    // Check boolean quota
    if (!currentPlan?.quotas?.canUseApp) {
      return NextResponse.json(
        { error: 'Upgrade to Pro to access this feature' },
        { status: 403 }
      )
    }

    // Your feature logic
    return NextResponse.json({ success: true })
  },
  OrganizationRole.MEMBER
)

Check numerical quotas:

export const POST = withOrganizationAuthRequired(
  async (req, context) => {
    const organization = await context.session.organization
    const projectLimit = organization.plan?.quotas?.numberOfThings ?? 3
    const currentCount = await getProjectCount(organization.id)

    if (currentCount >= projectLimit) {
      return NextResponse.json(
        { error: 'Limit reached', current: currentCount, limit: projectLimit },
        { status: 403 }
      )
    }

    return NextResponse.json({ success: true })
  },
  OrganizationRole.MEMBER
)

Reusable Feature Gate

function FeatureGate({ children, requiredQuota = 'canUseApp' }) {
  const { currentPlan } = useCurrentPlan()
  const hasAccess = currentPlan?.quotas?.[requiredQuota]

  if (!hasAccess) {
    return (
      <div className="p-6 border rounded-lg text-center">
        <Lock className="w-12 h-12 mx-auto mb-4" />
        <p className="text-muted-foreground mb-4">Upgrade to access this feature</p>
        <Link href="/pricing"><Button>View Plans</Button></Link>
      </div>
    )
  }

  return <>{children}</>
}

// Usage
<FeatureGate requiredQuota="canUseApp">
  <PremiumFeature />
</FeatureGate>

Related

  • Subscriptions - Create subscription flows
  • Dodo Setup - Configure payments
  • Super Admin - Manage plans

Subscriptions

Start earning recurring revenue with subscriptions - monthly, yearly, and trial periods out of the box.

Setting Up Your Database

Indie Kit supports multiple database providers. Choose the one that best fits your needs.

On this page

Plan-Based RenderingVideo WalkthroughClient-Side: useCurrentPlan HookConfigure Plan QuotasEnforce Quotas in ComponentsBest PracticesServer-Side: API RoutesReusable Feature GateRelated