Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
34caf20
completionId: cgen-6b274d2a04c040398521c9227a4c740b
builderio-bot Aug 14, 2025
a541ff2
Todo list updated
builderio-bot Aug 14, 2025
036461b
Todo list updated
builderio-bot Aug 14, 2025
8b0c2c8
Add alt text input option to CloudinaryImage component registry
builderio-bot Aug 14, 2025
53382a9
Update CloudinaryImage to use altText prop from Builder.io
builderio-bot Aug 14, 2025
a4cd9ba
Update Next.js to version that includes URL.canParse polyfill
builderio-bot Aug 14, 2025
4bca219
completionId: cgen-3e905d4a5da844d3b806de624ea5c48f
builderio-bot Aug 14, 2025
44446b0
Todo list updated
builderio-bot Aug 14, 2025
efee21f
Create checkout page directory and component
builderio-bot Aug 14, 2025
6021b42
Create CheckoutForm component
builderio-bot Aug 14, 2025
f604fcb
Create OrderSummary component
builderio-bot Aug 14, 2025
4255756
Create Stripe payment intent API endpoint
builderio-bot Aug 14, 2025
cd5b49b
Create checkout success page
builderio-bot Aug 14, 2025
c00e05d
Todo list updated
builderio-bot Aug 14, 2025
4c24194
Create Badge UI component
builderio-bot Aug 14, 2025
af35289
Update ProductDetails to integrate with cart store
builderio-bot Aug 14, 2025
c8bb535
Fix missing alt attribute in ImageHero component
builderio-bot Aug 14, 2025
17c3794
Add fake data to cart store for demonstration
builderio-bot Aug 14, 2025
144cc75
Fix empty alt attribute in ProductDetails ProductImage
builderio-bot Aug 14, 2025
638a4a1
Fix potential missing alt attribute in ProductDetails ImageSelector
builderio-bot Aug 14, 2025
1e87ef7
Fix empty alt attribute fallback in UpsellPopup
builderio-bot Aug 14, 2025
21a9b43
Fix cart badge positioning by adding proper container styling
builderio-bot Aug 14, 2025
37c3395
Improve error handling in Stripe API route
builderio-bot Aug 14, 2025
84864d1
Add Stripe configuration check in API route
builderio-bot Aug 14, 2025
04a7d64
Add error handling for missing Stripe configuration in checkout
builderio-bot Aug 14, 2025
de8a2cf
Add Stripe configuration check in checkout component
builderio-bot Aug 14, 2025
c442703
Add fallback UI when Stripe is not configured
builderio-bot Aug 14, 2025
1690981
Move cart badge to the left of the cart icon
builderio-bot Aug 14, 2025
c9cc17a
Add more dummy products to cart from homepage products
builderio-bot Aug 14, 2025
db83aad
Replace IoCartOutline with a more reliable cart icon
builderio-bot Aug 14, 2025
727ea0b
Update cart icon usage from IoCartOutline to ShoppingCart
builderio-bot Aug 14, 2025
50e59f1
Update empty cart icon from IoCartOutline to ShoppingCart
builderio-bot Aug 14, 2025
4ea1d14
Add hydration handling to cart store to prevent SSR mismatch
builderio-bot Aug 14, 2025
e462fb6
Add hasHydrated state and setHasHydrated action to cart store
builderio-bot Aug 14, 2025
908a2c9
Update persist configuration to exclude hasHydrated from persistence
builderio-bot Aug 14, 2025
7a3cc89
Add hydration handling to CartSlider component
builderio-bot Aug 14, 2025
98e64f5
Add useEffect to handle hydration in CartSlider
builderio-bot Aug 14, 2025
4c6e06a
Add hydration effect and conditional rendering for cart badge
builderio-bot Aug 14, 2025
045a2bb
Conditionally render cart badge only after hydration
builderio-bot Aug 14, 2025
b4f2a86
Update cart title to handle hydration state
builderio-bot Aug 14, 2025
8b747c8
Fix missing alt attribute fallback in ProductBox component
builderio-bot Aug 14, 2025
11af905
Fix persist rehydration callback in cart store
builderio-bot Aug 14, 2025
2dbbfad
Add initialization check to populate cart with demo data if empty
builderio-bot Aug 14, 2025
555411b
Reset initial cart state to empty and fix demo data loading
builderio-bot Aug 14, 2025
6f08acb
Simplify setHasHydrated to load demo data for empty carts
builderio-bot Aug 14, 2025
7ba411d
Fix response handling in checkout page to prevent double JSON parsing
builderio-bot Aug 14, 2025
ff58b91
Improve Stripe initialization with better error handling
builderio-bot Aug 14, 2025
4b05450
Create demo mode for checkout without requiring real Stripe keys
builderio-bot Aug 14, 2025
f6b0b93
Add demo mode handling in payment intent API
builderio-bot Aug 14, 2025
546f58b
Update checkout page to handle demo mode and fix Stripe initialization
builderio-bot Aug 14, 2025
baf7423
Add demo mode state to checkout page
builderio-bot Aug 14, 2025
1efcf67
Update checkout page to handle demo mode response
builderio-bot Aug 14, 2025
efab885
Update payment form rendering to handle demo mode properly
builderio-bot Aug 14, 2025
7aa2769
Replace single demo payment button with success and fail buttons
builderio-bot Aug 14, 2025
210377a
Update success page to handle demo mode failure parameter
builderio-bot Aug 14, 2025
38c45d3
Add cart store usage to ProductDetails component
builderio-bot Aug 14, 2025
1d947bf
Implement actual add to cart functionality in ProductDetails
builderio-bot Aug 14, 2025
df6cc16
Add user feedback when item is added to cart
builderio-bot Aug 14, 2025
3a5a2e5
Enhanced add to cart with feedback and cart opening
builderio-bot Aug 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions ecommerce/app/api/create-payment-intent/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { NextRequest, NextResponse } from 'next/server';
import Stripe from 'stripe';

const stripeSecretKey = process.env.STRIPE_SECRET_KEY;
const isDemoMode = !stripeSecretKey || stripeSecretKey.startsWith('sk_test_demo_') || stripeSecretKey.includes('demo');

if (isDemoMode) {
console.log('Running in demo mode - Stripe payments will be simulated');
}

let stripe: Stripe | null = null;
if (stripeSecretKey && !isDemoMode) {
try {
stripe = new Stripe(stripeSecretKey, {
apiVersion: '2024-12-18.acacia',
});
} catch (error) {
console.error('Failed to initialize Stripe:', error);
}
}

export async function POST(request: NextRequest) {
try {
const { amount, currency = 'usd', items } = await request.json();

// Validate the request
if (!amount || amount < 50) { // Minimum 50 cents
return NextResponse.json(
{ error: 'Amount must be at least 50 cents' },
{ status: 400 }
);
}

// Demo mode - return a fake client secret for testing
if (isDemoMode) {
console.log('Demo mode: Creating fake payment intent for amount:', amount);
return NextResponse.json({
clientSecret: 'pi_demo_1234567890_secret_demo123',
demoMode: true,
});
}

// Real Stripe mode
if (!stripe) {
return NextResponse.json(
{ error: 'Payment processing is not configured. Please add valid Stripe API keys.' },
{ status: 503 }
);
}

// Create a PaymentIntent with the order amount and currency
const paymentIntent = await stripe.paymentIntents.create({
amount: Math.round(amount),
currency,
automatic_payment_methods: {
enabled: true,
},
metadata: {
items: JSON.stringify(items?.map((item: any) => ({
id: item.id,
name: item.name,
quantity: item.quantity,
price: item.price,
})) || []),
},
});

return NextResponse.json({
clientSecret: paymentIntent.client_secret,
});
} catch (error) {
console.error('Error creating payment intent:', error);

// If Stripe error, fall back to demo mode
if (error instanceof Error && error.message.includes('Invalid API Key')) {
console.log('Invalid Stripe key detected, falling back to demo mode');
return NextResponse.json({
clientSecret: 'pi_demo_1234567890_secret_demo123',
demoMode: true,
});
}

return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
207 changes: 207 additions & 0 deletions ecommerce/app/checkout/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
"use client";

import { useState, useEffect } from 'react';
import { Elements } from '@stripe/react-stripe-js';
import { loadStripe } from '@stripe/stripe-js';
import { useCartStore } from '@/src/store/cartStore';
import { CheckoutForm } from '@/src/components/Checkout/CheckoutForm';
import { OrderSummary } from '@/src/components/Checkout/OrderSummary';
import { Button } from '@/src/components/ui/button';
import Link from 'next/link';
import { ArrowLeft } from 'lucide-react';

// Initialize Stripe (replace with your public key)
const getStripePromise = () => {
const publishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
const isDemoMode = !publishableKey || publishableKey.startsWith('pk_test_demo_') || publishableKey.includes('demo');

if (isDemoMode) {
console.log('Running in demo mode - Stripe will be simulated');
return null;
}

if (!publishableKey) {
console.warn('NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY not configured');
return null;
}

try {
return loadStripe(publishableKey);
} catch (error) {
console.error('Failed to initialize Stripe:', error);
return null;
}
};

const stripePromise = getStripePromise();

export default function CheckoutPage() {
const { items, getTotalPrice, getTotalItems } = useCartStore();
const [clientSecret, setClientSecret] = useState<string>('');
const [loading, setLoading] = useState(true);
const [demoMode, setDemoMode] = useState(false);

// Create payment intent when component mounts
useEffect(() => {
const createPaymentIntent = async () => {
if (items.length === 0) {
setLoading(false);
return;
}

try {
const response = await fetch('/api/create-payment-intent', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: Math.round(getTotalPrice() * 100), // Convert to cents
currency: 'usd',
items: items,
}),
});

if (!response.ok) {
const errorText = await response.text();
console.error('Payment API error:', response.status, errorText);
return;
}

const data = await response.json();
if (data.error) {
console.error('Payment configuration error:', data.error);
// Still set loading to false so user can see the cart
} else {
setClientSecret(data.clientSecret);
if (data.demoMode) {
setDemoMode(true);
}
}
} catch (error) {
console.error('Error creating payment intent:', error);
} finally {
setLoading(false);
}
};

createPaymentIntent();
}, [items, getTotalPrice]);

if (items.length === 0) {
return (
<div className="container mx-auto px-4 py-8">
<div className="max-w-md mx-auto text-center">
<h1 className="text-2xl font-bold mb-4">Your cart is empty</h1>
<p className="text-gray-600 mb-6">Add some items to your cart before checking out.</p>
<Button asChild>
<Link href="/">Continue Shopping</Link>
</Button>
</div>
</div>
);
}

if (loading) {
return (
<div className="container mx-auto px-4 py-8">
<div className="flex justify-center items-center h-64">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div>
</div>
</div>
);
}

const appearance = {
theme: 'stripe' as const,
variables: {
colorPrimary: '#0570de',
colorBackground: '#ffffff',
colorText: '#30313d',
colorDanger: '#df1b41',
fontFamily: 'Ideal Sans, system-ui, sans-serif',
spacingUnit: '2px',
borderRadius: '4px',
},
};

const stripeOptions = {
clientSecret,
appearance,
};

return (
<div className="container mx-auto px-4 py-8">
<div className="max-w-6xl mx-auto">
{/* Header */}
<div className="flex items-center mb-8">
<Button variant="ghost" asChild className="mr-4">
<Link href="/">
<ArrowLeft className="h-4 w-4 mr-2" />
Back to Shop
</Link>
</Button>
<h1 className="text-3xl font-bold">Checkout</h1>
</div>

<div className="grid lg:grid-cols-2 gap-8">
{/* Order Summary */}
<div className="lg:order-2">
<OrderSummary />
</div>

{/* Payment Form */}
<div className="lg:order-1">
{clientSecret && !demoMode && stripePromise ? (
<Elements options={stripeOptions} stripe={stripePromise}>
<CheckoutForm />
</Elements>
) : demoMode ? (
<div className="bg-blue-50 border border-blue-200 rounded-lg p-6">
<h3 className="text-lg font-medium mb-2">🎭 Demo Mode</h3>
<p className="text-gray-600 mb-4">
This is a demonstration of the checkout flow. Payment processing is simulated.
</p>
<div className="space-y-4">
<div className="bg-white p-4 rounded border">
<h4 className="font-medium mb-2">Demo Payment</h4>
<p className="text-sm text-gray-600 mb-3">
In a real implementation, this would process the payment through Stripe.
</p>
<div className="grid grid-cols-2 gap-3">
<button
className="bg-green-600 text-white py-2 px-4 rounded hover:bg-green-700 transition-colors"
onClick={() => window.location.href = '/checkout/success?demo=true'}
>
Demo Payment Success
</button>
<button
className="bg-red-600 text-white py-2 px-4 rounded hover:bg-red-700 transition-colors"
onClick={() => window.location.href = '/checkout/success?demo=true&failed=true'}
>
Demo Payment Fail
</button>
</div>
</div>
</div>
<p className="text-xs text-gray-500 mt-4">
To enable real payments, configure valid Stripe API keys in the environment variables.
</p>
</div>
) : (
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-6">
<h3 className="text-lg font-medium mb-2">Payment Configuration Required</h3>
<p className="text-gray-600 mb-4">
Payment processing is not fully configured. This is a demo checkout page.
</p>
<p className="text-sm text-gray-500">
To enable real payments, configure your Stripe API keys in the environment variables.
</p>
</div>
)}
</div>
</div>
</div>
</div>
);
}
Loading