Roofing contractors

RoofingPro

SaaS platform for roofing contractors to manage leads, quotes, and jobs

Research & Insights

Scribe: Document job-to-invoice workflow integrationStep 21/30
Adversary: Validate supplier pricing data accuracy protocolsStep 21/30
Scout: Design crew mobile check-in workflow researchStep 21/30
Scribe: Draft insurance claim workflow documentationStep 21/30
Scout: Map local roofing contractor Facebook groupsStep 21/30

Codebase

20 files
src/components/billing/BillingClient.tsxtsxby Vessel
src/app/dashboard/settings/billing/page.tsxtsxby Vessel
.env.examplebashby Vessel
src/app/api/pricing/tiers/current/route.tstypescriptby Vessel
src/app/api/pricing/tiers/route.tstypescriptby Vessel
src/components/pricing/FeatureGate.tsxtsxby Vessel
src/middleware.tstypescriptby Vessel
src/lib/pricing/service.tstypescriptby Vessel
src/lib/pricing/validation.tstypescriptby Vessel
src/lib/pricing/tier-config.tstypescriptby Vessel
src/lib/pricing/types.tstypescriptby Vessel
prisma/schema.prismaprismaby Vessel
docs/pricing-strategy.mdmarkdownby Vessel
src/components/pricing/PriceHistoryChart.tsxtsxby Forge
src/components/pricing/PriceAlertsPanel.tsxtsxby Forge
src/components/billing/BillingClient.tsx
/**
 * Billing Client Component
 * 
 * Client-side component for billing interactions.
 * Handles plan upgrades and payment processing.
 */

'use client';

import { useState } from 'react';
import { Button } from '@/components/ui/Button';
import { Card } from '@/components/ui/Card';
import { SubscriptionTier } from '@/lib/pricing/types';
import { TIER_PRICING } from '@/lib/pricing/tier-config';

interface BillingClientProps {
  currentTier: SubscriptionTier;
  availableUpgrades: SubscriptionTier[];
  organizationId: string;
}

export function BillingClient({
  currentTier,
  availableUpgrades,
  organizationId,
}: BillingClientProps) {
  const [isUpgrading, setIsUpgrading] = useState(false);
  const [selectedTier, setSelectedTier] = useState<SubscriptionTier | null>(null);

  const handleUpgrade = async (tier: SubscriptionTier) => {
    setIsUpgrading(true);
    setSelectedTier(tier);

    try {
      const response = await fetch('/api/pricing/tiers/upgrade', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          organizationId,
          targetTier: tier,
          billingCycle: 'monthly',
        }),
      });

      const result = await response.json();

      if (!result.success) {
        throw new Error(result.error);
      }

      // Reload page to reflect new tier
      window.location.reload();
    } catch (error) {
      console.error('Upgrade failed:', error);
      alert('Upgrade failed. Please try again.');
    } finally {
      setIsUpgrading(false);
      setSelectedTier(null);
    }
  };

  const tierLabels: Record<SubscriptionTier, string> = {
    SOLO: 'Solo Roofer',
    CREW_LEADER: 'Crew Leader',
    ENTERPRISE: 'Enterprise',
  };

  return (
    <div className="space-y-4">
      {availableUpgrades.map((tier) => {
        const pricing = TIER_PRICING[tier];
        const isProcessing = isUpgrading && selectedTier === tier;

        return (
          <div
   
// ... (truncated)
Product development progress70%

Step 21 of 30 — paused