Skip to content

Claude/custom billing UI 011 c uum fz n vj8 xwyq m71vg18#135

Merged
Jackson57279 merged 1 commit intomasterfrom
claude/custom-billing-ui-011CUumFzNVj8XwyqM71vg18
Nov 8, 2025
Merged

Claude/custom billing UI 011 c uum fz n vj8 xwyq m71vg18#135
Jackson57279 merged 1 commit intomasterfrom
claude/custom-billing-ui-011CUumFzNVj8XwyqM71vg18

Conversation

@Jackson57279
Copy link
Owner

@Jackson57279 Jackson57279 commented Nov 8, 2025

Summary by CodeRabbit

  • New Features

    • Added a Free plan option to the pricing table with features list and Current Plan indicator.
  • Improvements

    • Enhanced pricing grid layout for better responsiveness across different numbers of pricing options.
    • Streamlined checkout experience with improved error handling and direct payment flow support.

@codecapyai
Copy link

codecapyai bot commented Nov 8, 2025

CodeCapy Review ₍ᐢ•(ܫ)•ᐢ₎

Codebase Summary

ZapDev is an AI-powered development platform that allows users to create and manage web applications in real-time using AI agents in sandboxes. The application features real-time preview, file management, conversational development, and smart subscription and billing management integrated with a robust Next.js and React-based UI.

PR Changes

This pull request updates the pricing page by replacing the old PricingTable component with a new CustomPricingTable. The layout has been adjusted to use a wider container with updated padding, spacing, and typography. In addition, the CustomPricingTable provides loading, error, and content states for billing plans. It also implements a monthly/annual toggle, displays free plan information, and manages checkout interactions such as redirection for paid plans.

Setup Instructions

  1. Install the package manager: sudo npm install -g pnpm
  2. Clone the repository and navigate into it: cd
  3. Install dependencies: pnpm install
  4. Set up environment variables by copying env.example to .env and filling in required API keys and database URLs
  5. Build the E2B template as documented if needed
  6. Start the development server: pnpm dev
  7. Open your browser and navigate to http://localhost:3000 to test the application

Generated Test Cases

1: Verify Pricing Page Layout and Content ❗️❗️❗️

Description: Tests the visual appearance and layout of the pricing page, ensuring that the new container dimensions, typography, and header content are rendered correctly.

Prerequisites:

  • User is not required to be logged in
  • Application is running on the development server

Steps:

  1. Start the development server (pnpm dev) and open the browser to http://localhost:3000.
  2. Navigate to the Pricing page (e.g., by clicking the 'Pricing' menu item or directly accessing the route).
  3. Verify that the container uses the 'max-w-7xl' and proper padding classes (px-4 sm:px-6 lg:px-8) to ensure a wider layout.
  4. Check that the header contains the text 'Simple, Transparent Pricing' and a subtext describing the plan options.
  5. Inspect the logo image element, and verify that it has an alt text 'ZapDev - AI Development Platform' with dimensions 60x60 (width and height).

Expected Result: The pricing page displays with the updated wider layout, correct padding, a prominently styled header, and the updated logo image visible as per the new specifications.

2: Verify CustomPricingTable Loading State ❗️❗️❗️

Description: Tests the scenario where the pricing table data is still being fetched, ensuring that users see appropriate loading indicators.

Prerequisites:

  • Application is running on the development server
  • Simulate slow response (e.g., using network throttling) or configure the pricing hook to delay loading

Steps:

  1. Open the pricing page in the browser.
  2. Simulate a network delay so that the usePricingTable hook remains in a loading state.
  3. Observe the CustomPricingTable component to confirm that a spinner (Loader2 icon) and the text 'Loading pricing plans...' are displayed in a centered layout.

Expected Result: The user sees a centralized loading spinner accompanied by 'Loading pricing plans...' text, indicating that pricing plans are being fetched.

3: Verify CustomPricingTable Error State ❗️❗️

Description: Tests that when there is an error fetching pricing plans, the correct error message is displayed to the user.

Prerequisites:

  • Application is running on the development server
  • Simulated error state for the usePricingTable hook (e.g., by mocking the hook to return an error)

Steps:

  1. Open the pricing page in the browser.
  2. Force the CustomPricingTable to enter an error state (e.g., by mocking the pricing hook to return an error).
  3. Verify that an error message is displayed in the component, showing 'Unable to load pricing plans' along with 'Please try again later'.

Expected Result: The pricing table area shows an error message in a centered layout, informing the user that pricing plans could not be loaded.

4: Verify Monthly/Annual Toggle Functionality ❗️❗️❗️

Description: Tests the interactive toggle which lets users switch between monthly and annual billing intervals, and checks that the UI reflects the selection.

Prerequisites:

  • Application is running on the development server
  • Pricing table data must include products with both monthly and annual interval groups

Steps:

  1. Open the pricing page where the CustomPricingTable is rendered.
  2. Observe the toggle area with 'Monthly' and 'Annual' buttons. Confirm that by default, the Monthly option is active.
  3. Click on the 'Annual' button. Verify that the 'Annual' button now has visual indicators (e.g., bg-primary with text-primary-foreground, and a 'Save 20%' badge is visible).
  4. Click on the 'Monthly' button to switch back and verify that the toggle updates accordingly.
  5. Optionally, check that the pricing cards displayed update to reflect the chosen billing interval.

Expected Result: Users can toggle between monthly and annual billing. The active toggle button displays as highlighted, and the pricing cards update to display the correct interval-specific products.

5: Verify Free Plan Card Rendering ❗️❗️❗️

Description: Tests the rendering of the free plan card, ensuring that the plan name, description, and features list are displayed correctly with the disabled current plan button.

Prerequisites:

  • Application is running on the development server
  • Pricing table data includes a 'Free' product plan

Steps:

  1. Open the pricing page in the browser.
  2. Scroll to the pricing cards grid and locate the card representing the free plan.
  3. Verify that the card displays the plan name 'Free' and the description 'Perfect for trying out ZapDev'.
  4. Confirm that the price is displayed as '$0' with a '/month' label.
  5. Check that the features list (e.g. number of generations, frameworks, etc.) is rendered as specified.
  6. Ensure that the CTA button is disabled and reads 'Current Plan', with a note below that says 'Your current plan'.

Expected Result: The free plan card is rendered correctly with all designated texts, features, and a disabled CTA button indicating that it is the current plan.

6: Verify Pricing Card Checkout Redirection ❗️❗️❗️

Description: Tests the checkout flow for a pricing card by simulating a user clicking the upgrade button and ensuring that the system processes the checkout and redirects appropriately.

Prerequisites:

  • Application is running on the development server
  • Pricing table data includes a paid product with valid checkout details
  • A test customer context is provided so that the checkout function can execute

Steps:

  1. Open the pricing page and wait for the CustomPricingTable to load all pricing cards.
  2. Identify a paid plan pricing card that is not the current plan.
  3. Click on the CTA button for that pricing card.
  4. Observe that the button shows a loading spinner (Loader2 icon) to indicate processing.
  5. Simulate a successful checkout response by providing a mock checkout URL.
  6. Verify that the browser navigates (or the window.location.href is set) to the expected checkout URL.

Expected Result: Clicking the checkout button initiates a loading state and then redirects the user to the checkout URL provided by the mocked checkout response.

Raw Changes Analyzed
File: src/app/(home)/pricing/page-content.tsx
Changes:
@@ -1,26 +1,30 @@
 "use client";
 
 import Image from "next/image";
-import PricingTable from "@/components/autumn/pricing-table";
+import CustomPricingTable from "@/components/autumn/custom-pricing-table";
 
 export function PricingPageContent() {
   return (
-    <div className="flex flex-col max-w-3xl mx-auto w-full">
-      <section className="space-y-6 pt-[16vh] 2xl:pt-48">
-        <div className="flex flex-col items-center">
+    <div className="flex flex-col max-w-7xl mx-auto w-full px-4 sm:px-6 lg:px-8">
+      <section className="space-y-12 pt-[16vh] 2xl:pt-48 pb-24">
+        <div className="flex flex-col items-center space-y-6">
           <Image
             src="/logo.svg"
             alt="ZapDev - AI Development Platform"
-            width={50}
-            height={50}
+            width={60}
+            height={60}
             className="hidden md:block"
           />
+          <div className="text-center space-y-4 max-w-3xl">
+            <h1 className="text-4xl md:text-5xl font-bold bg-gradient-to-r from-foreground to-foreground/70 bg-clip-text text-transparent">
+              Simple, Transparent Pricing
+            </h1>
+            <p className="text-muted-foreground text-lg md:text-xl">
+              Choose the perfect plan for your development needs. Upgrade or downgrade at any time.
+            </p>
+          </div>
         </div>
-        <h1 className="text-xl md:text-3xl font-bold text-center">Pricing</h1>
-        <p className="text-muted-foreground text-center text-sm md:text-base">
-          Choose the plan that fits your needs
-        </p>
-        <PricingTable />
+        <CustomPricingTable />
       </section>
     </div>
    );

File: src/components/autumn/custom-pricing-table.tsx
Changes:
@@ -0,0 +1,403 @@
+'use client';
+
+import React, { useState } from "react";
+import { useCustomer, usePricingTable, type ProductDetails } from "autumn-js/react";
+import type { Product, ProductItem } from "autumn-js";
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/ui/button";
+import { Switch } from "@/components/ui/switch";
+import { Badge } from "@/components/ui/badge";
+import { getPricingTableContent } from "@/lib/autumn/pricing-table-content";
+import { Check, Loader2, Sparkles, Zap } from "lucide-react";
+
+interface CustomPricingTableProps {
+  productDetails?: ProductDetails[];
+  className?: string;
+}
+
+export default function CustomPricingTable({ productDetails, className }: CustomPricingTableProps) {
+  const { customer, checkout } = useCustomer({ errorOnNotFound: false });
+  const [isAnnual, setIsAnnual] = useState(false);
+  const { products, isLoading, error } = usePricingTable({ productDetails });
+
+  if (isLoading) {
+    return (
+      <div className="w-full h-full flex justify-center items-center min-h-[400px]">
+        <div className="flex flex-col items-center gap-3">
+          <Loader2 className="w-8 h-8 text-primary animate-spin" />
+          <p className="text-sm text-muted-foreground">Loading pricing plans...</p>
+        </div>
+      </div>
+    );
+  }
+
+  if (error) {
+    return (
+      <div className="w-full h-full flex justify-center items-center min-h-[400px]">
+        <div className="text-center space-y-2">
+          <p className="text-destructive font-medium">Unable to load pricing plans</p>
+          <p className="text-sm text-muted-foreground">Please try again later</p>
+        </div>
+      </div>
+    );
+  }
+
+  if (!products || products.length === 0) {
+    return null;
+  }
+
+  // Check if we have multiple intervals (monthly/annual)
+  const intervalGroups = products
+    .map((p) => p.properties?.interval_group)
+    .filter((intervalGroup): intervalGroup is string => Boolean(intervalGroup));
+  const intervals = Array.from(new Set(intervalGroups));
+  const multiInterval = intervals.length > 1;
+
+  // Filter products based on selected interval
+  const intervalFilter = (product: Product) => {
+    if (!product.properties?.interval_group) {
+      return true;
+    }
+
+    if (multiInterval) {
+      if (isAnnual) {
+        return product.properties?.interval_group === "year";
+      } else {
+        return product.properties?.interval_group === "month";
+      }
+    }
+
+    return true;
+  };
+
+  const filteredProducts = products.filter(intervalFilter);
+  const hasRecommended = filteredProducts.some((p) => p.display?.recommend_text);
+
+  return (
+    <div className={cn("w-full", className)}>
+      {/* Annual/Monthly Toggle */}
+      {multiInterval && (
+        <div className="flex justify-center mb-12">
+          <div className="inline-flex items-center gap-3 p-1 rounded-full bg-secondary/50 border">
+            <button
+              onClick={() => setIsAnnual(false)}
+              className={cn(
+                "px-6 py-2 rounded-full text-sm font-medium transition-all",
+                !isAnnual
+                  ? "bg-primary text-primary-foreground shadow-sm"
+                  : "text-muted-foreground hover:text-foreground"
+              )}
+            >
+              Monthly
+            </button>
+            <button
+              onClick={() => setIsAnnual(true)}
+              className={cn(
+                "px-6 py-2 rounded-full text-sm font-medium transition-all relative",
+                isAnnual
+                  ? "bg-primary text-primary-foreground shadow-sm"
+                  : "text-muted-foreground hover:text-foreground"
+              )}
+            >
+              Annual
+              <Badge className="ml-2 bg-green-500 text-white border-0 text-[10px] py-0 px-1.5">
+                Save 20%
+              </Badge>
+            </button>
+          </div>
+        </div>
+      )}
+
+      {/* Pricing Cards Grid */}
+      <div className={cn(
+        "grid gap-8 max-w-7xl mx-auto",
+        filteredProducts.length === 0 && "grid-cols-1 max-w-md",
+        filteredProducts.length === 1 && "grid-cols-1 md:grid-cols-2",
+        filteredProducts.length === 2 && "grid-cols-1 md:grid-cols-3",
+        filteredProducts.length >= 3 && "grid-cols-1 md:grid-cols-2 lg:grid-cols-4"
+      )}>
+        <FreePlanCard />
+        {filteredProducts.map((product, index) => (
+          <PricingCard
+            key={product.id ?? index}
+            product={product}
+            customer={customer}
+            checkout={checkout}
+            isRecommended={hasRecommended && Boolean(product.display?.recommend_text)}
+          />
+        ))}
+      </div>
+    </div>
+  );
+}
+
+function FreePlanCard() {
+  return (
+    <div className="relative flex flex-col rounded-2xl border bg-card transition-all duration-300 shadow-sm hover:shadow-md">
+      <div className="flex-1 p-8 space-y-6">
+        {/* Plan Name & Description */}
+        <div className="space-y-2">
+          <h3 className="text-2xl font-bold">Free</h3>
+          <p className="text-sm text-muted-foreground line-clamp-2">
+            Perfect for trying out ZapDev
+          </p>
+        </div>
+
+        {/* Price */}
+        <div className="space-y-1">
+          <div className="flex items-baseline gap-1">
+            <span className="text-4xl font-bold tracking-tight">$0</span>
+            <span className="text-muted-foreground">/month</span>
+          </div>
+        </div>
+
+        {/* Features List */}
+        <div className="space-y-3 pt-6 border-t">
+          <ul className="space-y-3">
+            <li className="flex items-start gap-3">
+              <div className="mt-0.5 shrink-0">
+                <div className="w-5 h-5 rounded-full flex items-center justify-center bg-muted text-muted-foreground">
+                  <Check className="w-3 h-3" />
+                </div>
+              </div>
+              <div className="flex-1 space-y-0.5">
+                <p className="text-sm font-medium leading-tight">5 generations daily</p>
+                <p className="text-xs text-muted-foreground">Resets every 24 hours</p>
+              </div>
+            </li>
+            <li className="flex items-start gap-3">
+              <div className="mt-0.5 shrink-0">
+                <div className="w-5 h-5 rounded-full flex items-center justify-center bg-muted text-muted-foreground">
+                  <Check className="w-3 h-3" />
+                </div>
+              </div>
+              <div className="flex-1 space-y-0.5">
+                <p className="text-sm font-medium leading-tight">All frameworks</p>
+                <p className="text-xs text-muted-foreground">Next.js, React, Vue, Angular, Svelte</p>
+              </div>
+            </li>
+            <li className="flex items-start gap-3">
+              <div className="mt-0.5 shrink-0">
+                <div className="w-5 h-5 rounded-full flex items-center justify-center bg-muted text-muted-foreground">
+                  <Check className="w-3 h-3" />
+                </div>
+              </div>
+              <div className="flex-1 space-y-0.5">
+                <p className="text-sm font-medium leading-tight">Real-time preview</p>
+                <p className="text-xs text-muted-foreground">Live sandbox environment</p>
+              </div>
+            </li>
+          </ul>
+        </div>
+      </div>
+
+      {/* CTA Button */}
+      <div className="p-8 pt-0">
+        <Button
+          variant="outline"
+          className="w-full h-11 text-base font-semibold transition-all"
+          disabled
+        >
+          <span className="flex items-center gap-2">
+            <Check className="w-4 h-4" />
+            Current Plan
+          </span>
+        </Button>
+        <p className="text-xs text-center text-muted-foreground mt-3">
+          Your current plan
+        </p>
+      </div>
+    </div>
+  );
+}
+
+interface PricingCardProps {
+  product: Product;
+  customer: any;
+  checkout: any;
+  isRecommended: boolean;
+}
+
+function PricingCard({ product, customer, checkout, isRecommended }: PricingCardProps) {
+  const [loading, setLoading] = useState(false);
+  const { buttonText } = getPricingTableContent(product);
+
+  const handleClick = async () => {
+    if (loading) return;
+
+    setLoading(true);
+    try {
+      if (product.id && customer) {
+        // Get checkout data and redirect to Stripe
+        const { data, error } = await checkout({
+          productId: product.id,
+        });
+
+        if (error) {
+          console.error("Checkout error:", error);
+          return;
+        }
+
+        // Redirect to Stripe checkout URL if available
+        if (data?.url) {
+          window.location.href = data.url;
+        }
+      } else if (product.display?.button_url) {
+        window.open(product.display?.button_url, "_blank", "noopener,noreferrer");
+      }
+    } catch (error) {
+      console.error("Checkout error:", error);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  const isDisabled =
+    loading ||
+    (product.scenario === "active" && !product.properties.updateable) ||
+    product.scenario === "scheduled";
+
+  // Get main price
+  const mainPriceDisplay = product.properties?.is_free
+    ? {
+        primary_text: "Free",
+        secondary_text: "",
+      }
+    : product.items?.[0]?.display ?? {
+        primary_text: "Custom",
+        secondary_text: "",
+      };
+
+  // Get features (skip first item if it's the price)
+  const featureItems = product.properties?.is_free
+    ? product.items ?? []
+    : (product.items?.length ?? 0) > 1
+      ? product.items.slice(1)
+      : [];
+
+  // Determine if this is the current plan
+  const isCurrentPlan = product.scenario === "active";
+
+  return (
+    <div
+      className={cn(
+        "relative flex flex-col rounded-2xl border bg-card transition-all duration-300",
+        isRecommended && [
+          "md:scale-105 shadow-xl border-primary/50",
+          "bg-gradient-to-b from-primary/5 to-card"
+        ],
+        !isRecommended && "shadow-sm hover:shadow-md",
+        isCurrentPlan && "ring-2 ring-primary/20"
+      )}
+    >
+      {/* Recommended Badge */}
+      {isRecommended && product.display?.recommend_text && (
+        <div className="absolute -top-4 left-1/2 -translate-x-1/2 z-10">
+          <Badge className="bg-gradient-to-r from-primary to-primary/80 text-primary-foreground border-0 shadow-lg px-4 py-1">
+            <Sparkles className="w-3 h-3 mr-1" />
+            {product.display.recommend_text}
+          </Badge>
+        </div>
+      )}
+
+      <div className="flex-1 p-8 space-y-6">
+        {/* Plan Name & Description */}
+        <div className="space-y-2">
+          <h3 className="text-2xl font-bold">
+            {product.display?.name || product.name}
+          </h3>
+          {product.display?.description && (
+            <p className="text-sm text-muted-foreground line-clamp-2">
+              {product.display.description}
+            </p>
+          )}
+        </div>
+
+        {/* Price */}
+        <div className="space-y-1">
+          <div className="flex items-baseline gap-1">
+            {mainPriceDisplay.primary_text && (
+              <span className="text-4xl font-bold tracking-tight">
+                {mainPriceDisplay.primary_text}
+              </span>
+            )}
+            {mainPriceDisplay.secondary_text && (
+              <span className="text-muted-foreground">
+                {mainPriceDisplay.secondary_text}
+              </span>
+            )}
+          </div>
+        </div>
+
+        {/* Features List */}
+        {featureItems.length > 0 && (
+          <div className="space-y-3 pt-6 border-t">
+            {product.display?.everything_from && (
+              <p className="text-sm text-muted-foreground mb-3">
+                Everything from {product.display.everything_from}, plus:
+              </p>
+            )}
+            <ul className="space-y-3">
+              {featureItems.map((item, index) => (
+                <li key={index} className="flex items-start gap-3">
+                  <div className="mt-0.5 shrink-0">
+                    <div className={cn(
+                      "w-5 h-5 rounded-full flex items-center justify-center",
+                      isRecommended
+                        ? "bg-primary/10 text-primary"
+                        : "bg-muted text-muted-foreground"
+                    )}>
+                      <Check className="w-3 h-3" />
+                    </div>
+                  </div>
+                  <div className="flex-1 space-y-0.5">
+                    <p className="text-sm font-medium leading-tight">
+                      {item.display?.primary_text}
+                    </p>
+                    {item.display?.secondary_text && (
+                      <p className="text-xs text-muted-foreground">
+                        {item.display.secondary_text}
+                      </p>
+                    )}
+                  </div>
+                </li>
+              ))}
+            </ul>
+          </div>
+        )}
+      </div>
+
+      {/* CTA Button */}
+      <div className="p-8 pt-0">
+        <Button
+          onClick={handleClick}
+          disabled={isDisabled}
+          className={cn(
+            "w-full h-11 text-base font-semibold transition-all group",
+            isRecommended && "bg-primary hover:bg-primary/90 shadow-lg hover:shadow-xl",
+            isCurrentPlan && "bg-secondary hover:bg-secondary/90 text-secondary-foreground"
+          )}
+          variant={isRecommended ? "default" : isCurrentPlan ? "secondary" : "outline"}
+        >
+          {loading ? (
+            <Loader2 className="w-4 h-4 animate-spin" />
+          ) : (
+            <span className="flex items-center gap-2">
+              {isCurrentPlan && <Check className="w-4 h-4" />}
+              {product.display?.button_text || buttonText}
+              {isRecommended && !isCurrentPlan && (
+                <Zap className="w-4 h-4 transition-transform group-hover:translate-x-0.5" />
+              )}
+            </span>
+          )}
+        </Button>
+
+        {isCurrentPlan && (
+          <p className="text-xs text-center text-muted-foreground mt-3">
+            Your current plan
+          </p>
+        )}
+      </div>
+    </div>
+  );
+}

@vercel
Copy link

vercel bot commented Nov 8, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
zapdev Ready Ready Preview Comment Nov 8, 2025 5:15am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 8, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

The pricing table component was refactored to remove CheckoutDialog dependency and replace it with a direct checkout flow. A FreePlanCard component was introduced as the first grid item, the grid layout logic updated to use responsive conditional classes, and the checkout click flow modified to handle URL redirection.

Changes

Cohort / File(s) Change Summary
Pricing Table Checkout and Layout Refactor
src/components/autumn/custom-pricing-table.tsx
Removed CheckoutDialog import and usage; introduced FreePlanCard component as first grid item; updated grid layout logic from fixed column mappings to responsive conditional classes based on product count (0/1/2/3+); refactored PricingCard click handler to perform direct checkout with error handling and URL redirect fallback; added FreePlanCard UI with static "Free" plan, features list, and disabled "Current Plan" CTA.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant PricingCard
    participant Checkout API
    participant Browser

    User->>PricingCard: Click pricing card
    alt Product and Customer exist
        PricingCard->>Checkout API: Call checkout
        alt Success with URL
            Checkout API-->>Browser: Return checkout URL
            Browser->>Browser: Redirect to URL
        else Success without URL
            Checkout API-->>PricingCard: Checkout initiated
            PricingCard->>Browser: Fallback to product button_url
            Browser->>Browser: Redirect to button_url
        else Error
            Checkout API-->>PricingCard: Error response
            PricingCard->>User: Display error (no dialog)
        end
    else Missing Product/Customer
        PricingCard->>Browser: Redirect to product button_url
        Browser->>Browser: Redirect to URL
    end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

  • Checkout flow modification: Verify the error handling and URL redirect logic are correct; ensure no edge cases are missed when product or customer is missing
  • FreePlanCard component: Confirm it mirrors existing card styling and integrates seamlessly with the grid layout
  • Grid layout logic: Review conditional class application for responsive behavior across product count scenarios (0/1/2/3+)

Possibly related PRs

Suggested labels

capy

Poem

🐰 A pricing table springs to life anew,
With FreePlanCard hops into the queue,
Checkout flows direct, no dialog delays,
Grid adapts and bends in responsive ways,
Direct redirects make checkout dreams come true! ✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The pull request title contains random characters and is not a clear summary of the actual changes made. Rename the title to clearly describe the main change, such as 'Add custom pricing table component with billing UI updates' or similar.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 52f5d67 and 9ef5c7d.

📒 Files selected for processing (1)
  • src/components/autumn/custom-pricing-table.tsx (3 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

- Remove checkout dialog in favor of direct Stripe redirect
- Add free plan card showing 5 generations daily
- Update grid layout to accommodate free plan alongside paid tiers
@codecapyai
Copy link

codecapyai bot commented Nov 8, 2025

🚀 Scrapybara Ubuntu instance started!

Interactive stream

⚠️ Error fetching GitHub variables, continuing setup:

status_code: 502, body: {'detail': "Error communicating with container: Client error '400 Bad Request' for url 'http://13.56.246.38:30024/env'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400"}

@codecapyai
Copy link

codecapyai bot commented Nov 8, 2025

🔧 Setting up test environment...

Agent Steps

@codecapyai
Copy link

codecapyai bot commented Nov 8, 2025

❌ Something went wrong:

status_code: 502, body: {'detail': {'error': "Unexpected error: Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'model: claude-3-5-sonnet-20241022'}, 'request_id': 'req_011CUurEDBwPNvFhFBYCtXBE'}", 'provider': 'anthropic', 'error_type': 'ProviderAPIError'}}

@Jackson57279 Jackson57279 force-pushed the claude/custom-billing-ui-011CUumFzNVj8XwyqM71vg18 branch from 02caaa1 to 9ef5c7d Compare November 8, 2025 05:13
@codecapyai
Copy link

codecapyai bot commented Nov 8, 2025

CodeCapy Review ₍ᐢ•(ܫ)•ᐢ₎

Codebase Summary

ZapDev is an AI-powered development platform built with Next.js, React, TypeScript, and Tailwind CSS. It offers real-time sandbox development, conversational project management, and subscription-based pro features. The application integrates various tools including Convex for backend operations, Clerk for authentication, and uses Inngest for background job processing.

PR Changes

This pull request introduces UI changes to the billing/pricing page by updating the custom pricing table component. Key changes include removing the CheckoutDialog import and dialog prop from the checkout function, revising the grid layout logic to dynamically adjust the grid columns based on the number of pricing products, and adding a new FreePlanCard that displays details of the free plan. These changes impact both the visual presentation as well as the checkout flow error handling.

Setup Instructions

  1. Install pnpm globally: sudo npm install -g pnpm
  2. Clone the repository and navigate into it.
  3. Run 'pnpm install' to install all dependencies.
  4. Start the development server by running 'pnpm dev'.
  5. Open your web browser and navigate to http://localhost:3000 to view the application.

Generated Test Cases

1: Render Free Plan Card and Grid Layout Adjustments ❗️❗️❗️

Description: Verifies that the pricing page displays the new FreePlanCard correctly and that the grid layout adjusts based on the number of pricing products.

Prerequisites:

  • User has navigated to the pricing page
  • The pricing products data is loaded (can be simulated/mocked)

Steps:

  1. Start the application and navigate to the pricing page (e.g., http://localhost:3000/pricing).
  2. Observe that the first card in the grid is the Free Plan card displaying the plan name 'Free', '$0/month', and the features list (5 generations daily, All frameworks, Real-time preview).
  3. Verify that the grid layout uses the correct CSS classes:
    • When there are no pricing products (filteredProducts length === 0), the container should have 'grid-cols-1 max-w-md'.
    • When there is one product, the container should have 'grid-cols-1 md:grid-cols-2'.
    • When there are two products, it should have 'grid-cols-1 md:grid-cols-3'.
    • For three or more products, it should have 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4'.

Expected Result: The pricing page should clearly display the Free Plan card at the beginning followed by the dynamically arranged pricing cards according to the number of products. The grid classes should match the conditions specified, ensuring proper spacing and layout.

2: Checkout Flow: Handling Checkout Errors ❗️❗️❗️

Description: Tests the checkout function flow when a pricing product is selected and simulates an error response to ensure proper error handling without performing a redirect.

Prerequisites:

  • User is logged in
  • A pricing product is available and the checkout function is mocked to return an error

Steps:

  1. Navigate to the pricing page and select a pricing product.
  2. Click on the pricing card's action button to initiate the checkout process.
  3. Simulate a checkout API response that returns an error (ensure the error object is populated).
  4. Observe the application behavior after clicking the button.

Expected Result: The application should log a checkout error in the console and not attempt to redirect the user (window.location.href should remain unchanged). The UI should remain on the pricing page without showing a CheckoutDialog, ensuring the error is handled gracefully.

3: Visual Verification of Free Plan Card Details ❗️❗️

Description: Ensures that the newly added FreePlanCard displays all the expected information and visual elements correctly.

Prerequisites:

  • User has navigated to the pricing page

Steps:

  1. Navigate to the pricing page (e.g., http://localhost:3000/pricing).
  2. Locate the FreePlanCard in the grid.
  3. Verify that the header displays the plan name 'Free' in a bold, large font.
  4. Confirm that the pricing details show '$0' followed by '/month'.
  5. Check the features list to ensure it includes the items: '5 generations daily' with additional info 'Resets every 24 hours', 'All frameworks' with additional info 'Next.js, React, Vue, Angular, Svelte', and 'Real-time preview' with additional info 'Live sandbox environment'.
  6. Inspect the CTA button to confirm it is styled as an outline button, disabled, shows a check icon and the text 'Current Plan', with a small subtext below indicating 'Your current plan'.

Expected Result: The FreePlanCard should render all details exactly as described: correct texts, icons, and layout. The CTA button should be disabled, and the overall visual appearance should match the upgraded design of the pricing page.

Raw Changes Analyzed
File: src/components/autumn/custom-pricing-table.tsx
Changes:
@@ -7,7 +7,6 @@ import { cn } from "@/lib/utils";
 import { Button } from "@/components/ui/button";
 import { Switch } from "@/components/ui/switch";
 import { Badge } from "@/components/ui/badge";
-import CheckoutDialog from "@/components/autumn/checkout-dialog";
 import { getPricingTableContent } from "@/lib/autumn/pricing-table-content";
 import { Check, Loader2, Sparkles, Zap } from "lucide-react";
 
@@ -112,11 +111,12 @@ export default function CustomPricingTable({ productDetails, className }: Custom
       {/* Pricing Cards Grid */}
       <div className={cn(
         "grid gap-8 max-w-7xl mx-auto",
-        filteredProducts.length === 1 && "grid-cols-1 max-w-md",
-        filteredProducts.length === 2 && "grid-cols-1 md:grid-cols-2",
-        filteredProducts.length === 3 && "grid-cols-1 md:grid-cols-3",
-        filteredProducts.length > 3 && "grid-cols-1 md:grid-cols-2 lg:grid-cols-4"
+        filteredProducts.length === 0 && "grid-cols-1 max-w-md",
+        filteredProducts.length === 1 && "grid-cols-1 md:grid-cols-2",
+        filteredProducts.length === 2 && "grid-cols-1 md:grid-cols-3",
+        filteredProducts.length >= 3 && "grid-cols-1 md:grid-cols-2 lg:grid-cols-4"
       )}>
+        <FreePlanCard />
         {filteredProducts.map((product, index) => (
           <PricingCard
             key={product.id ?? index}
@@ -131,6 +131,86 @@ export default function CustomPricingTable({ productDetails, className }: Custom
   );
 }
 
+function FreePlanCard() {
+  return (
+    <div className="relative flex flex-col rounded-2xl border bg-card transition-all duration-300 shadow-sm hover:shadow-md">
+      <div className="flex-1 p-8 space-y-6">
+        {/* Plan Name & Description */}
+        <div className="space-y-2">
+          <h3 className="text-2xl font-bold">Free</h3>
+          <p className="text-sm text-muted-foreground line-clamp-2">
+            Perfect for trying out ZapDev
+          </p>
+        </div>
+
+        {/* Price */}
+        <div className="space-y-1">
+          <div className="flex items-baseline gap-1">
+            <span className="text-4xl font-bold tracking-tight">$0</span>
+            <span className="text-muted-foreground">/month</span>
+          </div>
+        </div>
+
+        {/* Features List */}
+        <div className="space-y-3 pt-6 border-t">
+          <ul className="space-y-3">
+            <li className="flex items-start gap-3">
+              <div className="mt-0.5 shrink-0">
+                <div className="w-5 h-5 rounded-full flex items-center justify-center bg-muted text-muted-foreground">
+                  <Check className="w-3 h-3" />
+                </div>
+              </div>
+              <div className="flex-1 space-y-0.5">
+                <p className="text-sm font-medium leading-tight">5 generations daily</p>
+                <p className="text-xs text-muted-foreground">Resets every 24 hours</p>
+              </div>
+            </li>
+            <li className="flex items-start gap-3">
+              <div className="mt-0.5 shrink-0">
+                <div className="w-5 h-5 rounded-full flex items-center justify-center bg-muted text-muted-foreground">
+                  <Check className="w-3 h-3" />
+                </div>
+              </div>
+              <div className="flex-1 space-y-0.5">
+                <p className="text-sm font-medium leading-tight">All frameworks</p>
+                <p className="text-xs text-muted-foreground">Next.js, React, Vue, Angular, Svelte</p>
+              </div>
+            </li>
+            <li className="flex items-start gap-3">
+              <div className="mt-0.5 shrink-0">
+                <div className="w-5 h-5 rounded-full flex items-center justify-center bg-muted text-muted-foreground">
+                  <Check className="w-3 h-3" />
+                </div>
+              </div>
+              <div className="flex-1 space-y-0.5">
+                <p className="text-sm font-medium leading-tight">Real-time preview</p>
+                <p className="text-xs text-muted-foreground">Live sandbox environment</p>
+              </div>
+            </li>
+          </ul>
+        </div>
+      </div>
+
+      {/* CTA Button */}
+      <div className="p-8 pt-0">
+        <Button
+          variant="outline"
+          className="w-full h-11 text-base font-semibold transition-all"
+          disabled
+        >
+          <span className="flex items-center gap-2">
+            <Check className="w-4 h-4" />
+            Current Plan
+          </span>
+        </Button>
+        <p className="text-xs text-center text-muted-foreground mt-3">
+          Your current plan
+        </p>
+      </div>
+    </div>
+  );
+}
+
 interface PricingCardProps {
   product: Product;
   customer: any;
@@ -148,10 +228,20 @@ function PricingCard({ product, customer, checkout, isRecommended }: PricingCard
     setLoading(true);
     try {
       if (product.id && customer) {
-        await checkout({
+        // Get checkout data and redirect to Stripe
+        const { data, error } = await checkout({
           productId: product.id,
-          dialog: CheckoutDialog,
         });
+
+        if (error) {
+          console.error("Checkout error:", error);
+          return;
+        }
+
+        // Redirect to Stripe checkout URL if available
+        if (data?.url) {
+          window.location.href = data.url;
+        }
       } else if (product.display?.button_url) {
         window.open(product.display?.button_url, "_blank", "noopener,noreferrer");
       }

@codecapyai
Copy link

codecapyai bot commented Nov 8, 2025

🚀 Scrapybara Ubuntu instance started!

Interactive stream

⚠️ Error fetching GitHub variables, continuing setup:

status_code: 502, body: {'detail': "Error communicating with container: Client error '400 Bad Request' for url 'http://54.176.2.220:30002/env'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400"}

@codecapyai
Copy link

codecapyai bot commented Nov 8, 2025

🔧 Setting up test environment...

Agent Steps

@chatgpt-codex-connector
Copy link

💡 Codex Review

https://github.com/otdoges/zapdev/blob/02caaa103637958666abd7abb2c70b7d66a1cd4e/src/components/autumn/custom-pricing-table.tsx#L134-L207
P1 Badge Free plan card always disabled as current plan

The newly added free tier card renders a hard‑coded button labeled Current Plan with disabled set to true and a caption saying "Your current plan". Because it does not check the customer’s actual subscription status, paid users will still see the free tier presented as their current plan and have no way to downgrade. The previous implementation relied on the product data returned by usePricingTable to determine the scenario and button state. This static card breaks plan switching for anyone not already on the free tier and misrepresents their subscription.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@codecapyai
Copy link

codecapyai bot commented Nov 8, 2025

❌ Something went wrong:

status_code: 502, body: {'detail': {'error': "Unexpected error: Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'model: claude-3-5-sonnet-20241022'}, 'request_id': 'req_011CUurMezfHi3GZDGU9jb4H'}", 'provider': 'anthropic', 'error_type': 'ProviderAPIError'}}

@Jackson57279 Jackson57279 merged commit 781568b into master Nov 8, 2025
18 of 22 checks passed
@Jackson57279 Jackson57279 deleted the claude/custom-billing-ui-011CUumFzNVj8XwyqM71vg18 branch November 8, 2025 05:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants