Skip to content

Build Custom Billing UI with Autumn API#134

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

Build Custom Billing UI with Autumn API#134
Jackson57279 merged 1 commit intomasterfrom
claude/custom-billing-ui-011CUumFzNVj8XwyqM71vg18

Conversation

@Jackson57279
Copy link
Owner

@Jackson57279 Jackson57279 commented Nov 8, 2025

Replace default Autumn pricing table with custom implementation featuring gradient accents, responsive grid layout, annual/monthly toggle, and enhanced visual hierarchy for better user experience.

Summary by CodeRabbit

New Features

  • Redesigned pricing page layout with expanded width and improved spacing
  • Added monthly/annual billing toggle for flexible pricing interval selection
  • Introduced enhanced pricing cards with loading states and error handling
  • Updated pricing page hero section with refined visual hierarchy

Replace default Autumn pricing table with custom implementation featuring gradient accents, responsive grid layout, annual/monthly toggle, and enhanced visual hierarchy for better user experience.
@codecapyai
Copy link

codecapyai bot commented Nov 8, 2025

CodeCapy Review ₍ᐢ•(ܫ)•ᐢ₎

Codebase Summary

ZapDev is an AI-powered development platform enabling users to create web applications in real time. This project uses Next.js, React, TypeScript, Tailwind CSS, and various backend services for AI code generation and subscription management, among others. The recent changes update the billing page by replacing the default pricing table with a custom pricing table integrated with Autumn API.

PR Changes

The PR introduces a custom billing UI by replacing the default Autumn pricing table with a new CustomPricingTable component. This component features gradient accents, responsive grid layout, an annual/monthly toggle, and enhanced visual hierarchy. Changes include adjustments in the page layout, image size, and new UI interactions within the pricing table such as a toggle for pricing intervals, checkout action, and visual cues for recommended plans.

Setup Instructions

  1. Ensure Node.js is installed. Run: sudo npm install -g pnpm
  2. Clone the repository and navigate into it: cd path/to/zapdev
  3. Install dependencies: pnpm install
  4. Start the development server: pnpm dev
  5. Open a web browser and navigate to http://localhost:3000 to access the application.

Generated Test Cases

1: Verify Custom Pricing Table Loading State ❗️❗️❗️

Description: Tests that when the pricing table data is being fetched, a loading spinner and appropriate loading text are displayed, ensuring users are informed the pricing plans are being loaded.

Prerequisites:

  • User navigates to the pricing page (http://localhost:3000) with network conditions delay or simulated loading condition.

Steps:

  1. Navigate to the pricing page where the custom pricing table is rendered.
  2. Throttle the network connection (or simulate slow API response) to force the loading state.
  3. Observe the pricing table area for a loader icon (Loader2 component) and the text 'Loading pricing plans...'.

Expected Result: The pricing table section shows a centered spinner (Loader2) along with the text 'Loading pricing plans...' ensuring that users are aware that data is still loading.

2: Verify Custom Pricing Table Error State ❗️❗️❗️

Description: Ensures that if there is an error loading pricing plans, the UI displays an appropriate error message to inform the user and prompt them to try again later.

Prerequisites:

  • Simulate a failing API call or invoke an error response in the pricing table API (if possible via a testing flag or mock).

Steps:

  1. Simulate an API error by forcing the pricing data fetch to fail.
  2. Navigate to the pricing page.
  3. Observe the pricing table rendering area for an error message displayed in a centered layout.

Expected Result: The custom pricing table shows an error message with text 'Unable to load pricing plans' in red or destructive style and a supplementary message 'Please try again later'.

3: Toggle Between Annual and Monthly Pricing ❗️❗️❗️

Description: Tests the functionality of the annual/monthly toggle in the pricing table, ensuring that the correct set of pricing cards are displayed based on the selected interval.

Prerequisites:

  • Pricing data must include products with an 'interval_group' property for both monthly and annual intervals.
  • User must be on the pricing page.

Steps:

  1. Navigate to the pricing page and wait for the pricing table to load.
  2. Locate the toggle buttons labeled 'Monthly' and 'Annual' at the top of the pricing table.
  3. Click the 'Annual' button.
  4. Observe that the displayed pricing cards update to show annual plan details and that a 'Save 20%' badge appears on the 'Annual' button.
  5. Click the 'Monthly' button.
  6. Verify that the displayed pricing cards update to show the monthly plan details.

Expected Result: When 'Annual' is selected, pricing details update accordingly and the button indicates it is active (with a primary background and a 'Save 20%' badge). Switching back to 'Monthly' updates the pricing cards to display monthly pricing information.

4: Verify Checkout Functionality on Pricing Card ❗️❗️❗️

Description: Checks that clicking on a pricing card's checkout button initiates the checkout process, including a loading state and proper callback handling.

Prerequisites:

  • User must have a valid customer context (simulate logged in or provide a mock customer) so that checkout action is enabled.
  • At least one pricing card should be rendered on the pricing page with a valid product id.

Steps:

  1. Navigate to the pricing page and wait until the pricing table loads with products.
  2. Locate a pricing card with an active checkout button.
  3. Click the checkout button on this pricing card.
  4. Observe that the button enters a loading state (spinner appears inside the button) while processing the action.
  5. Verify that after a brief moment, either a checkout dialog opens (CheckoutDialog is rendered) or a new tab is opened if the product has a 'button_url'.

Expected Result: Upon clicking, the pricing card button shows a loader and then triggers a checkout process, either by opening a modal dialog for checkout (if integrated) or by navigating to an external URL, confirming that the process works as intended.

5: Verify Recommended Plan Badge Display ❗️❗️

Description: Tests that pricing cards marked as recommended display a badge with appropriate styling and icon, providing visual emphasis on recommended plans.

Prerequisites:

  • Pricing data must include at least one product with a 'recommend_text' field.
  • User must be on the pricing page.

Steps:

  1. Navigate to the pricing page and wait for the pricing table to fully load.
  2. Identify a pricing card that should be marked as recommended by checking for the presence of a badge.
  3. Inspect the pricing card to verify that a badge with gradient background, a Sparkles icon, and the recommended text is displayed near the top of the card.

Expected Result: The pricing card for a recommended plan should visibly show a badge with the recommended text, the Sparkles icon, and enhanced styling (e.g., scale effect and shadow) to draw user attention.

6: Verify Visual Layout and Responsive Design of Pricing Page ❗️❗️

Description: Ensures that the updated pricing page has the new layout, incorporating gradient-accented headers, proper spacing and a responsive grid layout for pricing cards.

Prerequisites:

  • User must be on a desktop browser for initial layout verification.

Steps:

  1. Navigate to the pricing page at http://localhost:3000 and wait for it to fully render.
  2. Check that the page contains a centrally aligned header with gradient text reading 'Simple, Transparent Pricing' and supporting subtext.
  3. Confirm that the pricing cards are arranged in a responsive grid layout, with grid settings altering based on the number of available pricing cards (e.g., one column for a single plan, two for two plans, etc.).
  4. Resize the browser window to check that the layout adjusts appropriately (for example, grid columns decrease on narrower widths).

Expected Result: The pricing page displays a visibly appealing header with the correct gradient effect and theme. The pricing cards should be arranged in a grid that adjusts based on screen size and the number of available products, ensuring a consistent and responsive design.

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,313 @@
+'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 CheckoutDialog from "@/components/autumn/checkout-dialog";
+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 === 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.map((product, index) => (
+          <PricingCard
+            key={product.id ?? index}
+            product={product}
+            customer={customer}
+            checkout={checkout}
+            isRecommended={hasRecommended && Boolean(product.display?.recommend_text)}
+          />
+        ))}
+      </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) {
+        await checkout({
+          productId: product.id,
+          dialog: CheckoutDialog,
+        });
+      } 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 4:52am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 8, 2025

Walkthrough

The pricing page is redesigned with a larger container, new hero text block, and increased logo size. The static pricing header is replaced with a new CustomPricingTable component that fetches pricing data, supports monthly/annual interval toggling, handles loading and error states, and integrates with a checkout flow via dialog or URL fallback.

Changes

Cohort / File(s) Change Summary
Pricing Page Layout Update
src/app/(home)/pricing/page-content.tsx
Expands container max-width to max-w-7xl, adds horizontal padding, increases logo from 50x50 to 60x60, introduces centered hero text section with heading and subheading, adjusts vertical spacing to space-y-12 with pb-24, replaces static header and description with CustomPricingTable component
New Pricing Table Component
src/components/autumn/custom-pricing-table.tsx
Adds new client-side React component that fetches and displays pricing cards with multi-interval (monthly/annual) support, handles loading/error states, toggles between billing intervals, integrates checkout dialog or URL fallback on CTA, includes per-card recommendation badge and current-plan indication, uses responsive grid layout with utility content from getPricingTableContent

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CustomPricingTable
    participant usePricingTable
    participant PricingCard
    participant checkout/URL

    User->>CustomPricingTable: Load pricing page
    activate CustomPricingTable
    CustomPricingTable->>usePricingTable: Fetch pricing data
    activate usePricingTable
    usePricingTable-->>CustomPricingTable: Return products & intervals
    deactivate usePricingTable
    
    rect rgb(200, 220, 255)
        Note over CustomPricingTable: Render cards with interval toggle
        CustomPricingTable->>CustomPricingTable: Toggle interval (monthly/annual)
    end

    User->>PricingCard: Click CTA button
    activate PricingCard
    
    rect rgb(255, 220, 200)
        alt Multiple intervals available
            PricingCard->>checkout/URL: checkout() → CheckoutDialog or URL
        else Single interval
            PricingCard->>checkout/URL: Direct URL navigation
        end
    end
    deactivate PricingCard
    deactivate CustomPricingTable
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • CustomPricingTable component (src/components/autumn/custom-pricing-table.tsx): Requires careful review of state management (interval toggling, loading states), checkout integration (dialog vs. URL fallback logic), error handling, and responsive grid behavior
  • Interval filtering logic: Verify the monthly/annual toggle correctly filters and displays the appropriate pricing cards
  • Checkout integration: Confirm the checkout flow properly handles both CheckoutDialog and URL fallback scenarios
  • Layout changes (page-content.tsx): Layout and spacing adjustments are straightforward but should be verified for visual alignment with design intent

Possibly related PRs

  • Integrate app payments and pricing model #58: New CustomPricingTable integrates directly with the Autumn checkout flow (checkout function, CheckoutDialog, useCustomer hook, server endpoints) to replace the previous pricing implementation.

Suggested labels

capy

Poem

🐰 A shiny pricing table hops into view,
With monthly and annual—take your pick, it's true!
Checkout dialogs spring forth with a gleeful bound,
Where autumn's magic and commerce are found.
The grid responsive, the cards all aglow,
Our pricing page blooms—what a delightful show! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Build Custom Billing UI with Autumn API' directly reflects the main changes: replacing the default Autumn pricing table with a custom implementation that integrates the Autumn API for billing.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/custom-billing-ui-011CUumFzNVj8XwyqM71vg18

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.

@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:30045/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_011CUupnKpTiAhFXvcduErfa'}", 'provider': 'anthropic', 'error_type': 'ProviderAPIError'}}

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ 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".

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 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 675f4a5 and 7dc3ada.

📒 Files selected for processing (2)
  • src/app/(home)/pricing/page-content.tsx (1 hunks)
  • src/components/autumn/custom-pricing-table.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
src/components/autumn/custom-pricing-table.tsx (4)
src/lib/utils.ts (1)
  • cn (6-8)
src/components/autumn/pricing-table.tsx (1)
  • PricingCard (185-285)
src/lib/autumn/pricing-table-content.tsx (1)
  • getPricingTableContent (3-66)
src/components/autumn/checkout-dialog.tsx (1)
  • CheckoutDialog (55-151)
src/app/(home)/pricing/page-content.tsx (1)
src/components/autumn/custom-pricing-table.tsx (1)
  • CustomPricingTable (19-132)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Codacy Security Scan
  • GitHub Check: claude-review

Comment on lines +150 to +158
if (product.id && customer) {
await checkout({
productId: product.id,
dialog: CheckoutDialog,
});
} else if (product.display?.button_url) {
window.open(product.display?.button_url, "_blank", "noopener,noreferrer");
}
} catch (error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove the customer gate before invoking checkout()

useCustomer().checkout() is documented to work without a populated customer object—it will create or fetch the customer on demand. Gating the call on customer means the CTA silently does nothing whenever the hook hasn’t resolved yet or when a brand-new visitor (no stored customer) clicks it and the product lacks a button_url, effectively breaking the purchase flow.(docs.useautumn.com)

Please drop the customer check so we always call checkout when product.id is available:

-      if (product.id && customer) {
+      if (product.id) {
         await checkout({
           productId: product.id,
           dialog: CheckoutDialog,
         });
       } else if (product.display?.button_url) {
         window.open(product.display?.button_url, "_blank", "noopener,noreferrer");
       }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (product.id && customer) {
await checkout({
productId: product.id,
dialog: CheckoutDialog,
});
} else if (product.display?.button_url) {
window.open(product.display?.button_url, "_blank", "noopener,noreferrer");
}
} catch (error) {
if (product.id) {
await checkout({
productId: product.id,
dialog: CheckoutDialog,
});
} else if (product.display?.button_url) {
window.open(product.display?.button_url, "_blank", "noopener,noreferrer");
}
} catch (error) {
🤖 Prompt for AI Agents
In src/components/autumn/custom-pricing-table.tsx around lines 150 to 158, the
current logic prevents calling checkout() unless a `customer` exists; remove the
`customer` check so that whenever `product.id` is present we call `checkout({
productId: product.id, dialog: CheckoutDialog })` unconditionally, and keep the
existing else branch that opens `product.display?.button_url` when `product.id`
is falsy; update the conditional to only test `product.id` (and ensure error
handling remains unchanged).

@claude
Copy link

claude bot commented Nov 8, 2025

Code Review: Custom Billing UI with Autumn API

✅ Strengths

  1. Improved UX - Modern gradient accents, responsive grid, intuitive annual/monthly toggle
  2. Code Quality - Clean structure, TypeScript types from autumn-js, good use of cn() utility
  3. Consistency - Reuses UI components (Button, Badge, Switch), follows Tailwind conventions

⚠️ Issues & Concerns

1. Type Safety (custom-pricing-table.tsx:40)

  • customer and checkout typed as any
  • Fix: Import proper types from autumn-js/react

2. Accessibility

  • Toggle buttons missing aria-pressed, aria-label
  • Pricing cards lack semantic structure
  • "Save 20%" badge needs aria-hidden="true"

3. Error Handling (lines 143-158)

  • Errors only logged to console, no user feedback
  • Fix: Add error state UI like original component (pricing-table.tsx:336-350)

4. Hardcoded Values

  • "Save 20%" hardcoded at line 103
  • Fix: Calculate from product data or make configurable

5. Loading State

  • Button disabled but no spinner shown during checkout

📊 Performance

⚠️ filteredProducts recalculated every render - use useMemo

📝 Test Coverage

Missing tests for new component

🔒 Security

✅ No concerns - proper noopener/noreferrer, no XSS risks

📋 Action Items

Priority 1 (Must Fix):

  1. Add proper TypeScript types (remove any)
  2. Implement error handling UI
  3. Add ARIA attributes

Priority 2 (Should Fix):

  1. Make "Save 20%" configurable
  2. Add loading spinner
  3. Add useMemo for filteredProducts

Priority 3:

  1. Add unit tests
  2. Test WCAG contrast

✅ Recommendation

⚠️ Approve with Changes - Excellent visual improvements! Address Priority 1 items before production merge.


Reviewed by: Claude Code | Files: 2 (+328, -11)

@Jackson57279 Jackson57279 merged commit 52f5d67 into master Nov 8, 2025
24 of 26 checks passed
@Jackson57279 Jackson57279 deleted the claude/custom-billing-ui-011CUumFzNVj8XwyqM71vg18 branch November 8, 2025 05:04
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