Claude/custom billing UI 011 c uum fz n vj8 xwyq m71vg18#135
Conversation
CodeCapy Review ₍ᐢ•(ܫ)•ᐢ₎Codebase SummaryZapDev 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 ChangesThis 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
Generated Test Cases1: 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:
Steps:
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:
Steps:
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:
Steps:
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:
Steps:
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:
Steps:
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:
Steps:
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 AnalyzedFile: 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>
+ );
+}
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Caution Review failedThe pull request is closed. WalkthroughThe 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
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
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes
Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro Disabled knowledge base sources:
📒 Files selected for processing (1)
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. Comment |
- 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
|
🚀 Scrapybara Ubuntu instance started! |
|
🔧 Setting up test environment... Agent Steps |
|
❌ Something went wrong: |
02caaa1 to
9ef5c7d
Compare
CodeCapy Review ₍ᐢ•(ܫ)•ᐢ₎Codebase SummaryZapDev 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 ChangesThis 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
Generated Test Cases1: 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:
Steps:
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:
Steps:
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:
Steps:
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 AnalyzedFile: 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");
}
|
|
🚀 Scrapybara Ubuntu instance started! |
|
🔧 Setting up test environment... Agent Steps |
💡 Codex Reviewhttps://github.com/otdoges/zapdev/blob/02caaa103637958666abd7abb2c70b7d66a1cd4e/src/components/autumn/custom-pricing-table.tsx#L134-L207 The newly added free tier card renders a hard‑coded button labeled ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
❌ Something went wrong: |
Summary by CodeRabbit
New Features
Improvements