-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Description
Problem
The checkout page component at src/app/store/[slug]/checkout/page.tsx is 1,039 lines and violates the separation of concerns principle by mixing:
- UI presentation logic (JSX rendering, form layout)
- Form state management (55+ state variables and hooks)
- Business logic (price calculations, discount validation, shipping cost calculation)
- API integration (order creation, discount application)
- Complex validation (billing/shipping address, payment methods, Pathao integration)
This makes the component:
- Difficult to test - business logic is coupled with React rendering
- Hard to maintain - changes require understanding the entire 1000+ line component
- Not reusable - validation and calculation logic can't be used elsewhere
- Slow to render - all logic runs in the browser on every render
Current Code Location
- File:
src/app/store/[slug]/checkout/page.tsx(lines 1-1039) - Component:
CheckoutPage(client component) - Complexity: Very High
- Hook count: 14 conditional statements, 55+ hooks/functions
Proposed Refactoring
Benefits
- Improved testability: Business logic can be tested without React
- Better performance: Move calculations to server components or hooks
- Code reusability: Validation and calculations can be used in other contexts
- Easier maintenance: Clear separation makes changes more predictable
- Type safety: Extracted functions have explicit type signatures
Suggested Approach
-
Extract form schema and validation →
src/lib/validation/checkout-validation.ts- Move
checkoutSchema(lines 57-113) - Extract validation refinements into testable functions
- Move
-
Extract business logic hooks →
src/hooks/useCheckoutCalculations.ts- Price calculations (subtotal, shipping, taxes, discounts, total)
- Discount code validation and application
- Shipping cost calculation based on location
-
Extract order processing →
src/lib/services/checkout.service.ts(may exist)- Move API integration logic
- Order creation workflow
- Error handling and retry logic
-
Extract form components →
src/components/checkout/CheckoutCustomerInfo.tsx- Email, name, phone fieldsCheckoutShippingAddress.tsx- Address form with Pathao integrationCheckoutBillingAddress.tsx- Optional billing addressCheckoutPaymentMethod.tsx- Payment method selectionCheckoutOrderSummary.tsx- Order summary, discount, totalCheckoutActions.tsx- Submit button and validation
-
Keep page component lean (target: <200 lines)
- Orchestrate form submission
- Handle navigation and routing
- Compose extracted components
Code Example
Before:
// src/app/store/[slug]/checkout/page.tsx (1039 lines)
"use client";
export default function CheckoutPage() {
// 55+ lines of state and hooks
const [isSubmitting, setIsSubmitting] = useState(false);
const [discount, setDiscount] = useState(Discount | null)(null);
const [discountCode, setDiscountCode] = useState("");
const [isApplyingDiscount, setIsApplyingDiscount] = useState(false);
// ... 50+ more state variables
// 100+ lines of validation schema
const checkoutSchema = z.object({
email: z.string().email("Please enter a valid email address"),
firstName: z.string().min(1, "First name is required"),
// ... 50+ more fields
}).refine((data) => {
// Complex validation logic
});
// 200+ lines of business logic
const calculateSubtotal = () => {
return cartItems.reduce((sum, item) => {
const effectivePrice = item.variant
? calculateEffectivePrice(item.variant)
: calculateEffectivePrice(item.product);
return sum + effectivePrice * item.quantity;
}, 0);
};
const calculateShipping = () => {
// Complex shipping calculation
};
const calculateDiscount = () => {
// Complex discount calculation
};
// 150+ lines of form handlers
const onSubmit = async (data: CheckoutFormValues) => {
// Complex order creation logic
};
const handleApplyDiscount = async () => {
// Discount application logic
};
// 500+ lines of JSX
return (
(form onSubmit={handleSubmit(onSubmit)})
{/* Massive form with deeply nested components */}
(/form)
);
}After:
// src/lib/validation/checkout-validation.ts (~150 lines)
import { z } from 'zod';
import { emailSchema, phoneSchema } from '@/lib/validation/schemas';
export const checkoutSchema = z.object({
email: emailSchema,
firstName: z.string().min(1, "First name is required"),
lastName: z.string().min(1, "Last name is required"),
phone: phoneSchema,
deliveryLocation: z.enum(["inside_dhaka", "outside_dhaka"]),
shippingAddress: z.string().min(5, "Address is required"),
// ... rest of schema
}).refine(validateBillingAddress, {
message: "Billing address is required when different from shipping",
});
export function validateBillingAddress(data: CheckoutFormData): boolean {
if (!data.billingSameAsShipping) {
return !!(data.billingAddress && data.billingCity && data.billingState);
}
return true;
}
export function validatePathaoFields(data: CheckoutFormData): boolean {
// Validation logic
}
export type CheckoutFormData = z.infer(typeof checkoutSchema);
// src/hooks/useCheckoutCalculations.ts (~200 lines)
import { CartItem } from '@/lib/stores/cart-store';
import { Discount } from '`@prisma/client`';
export function useCheckoutCalculations(
cartItems: CartItem[],
discount: Discount | null,
deliveryLocation: string
) {
const subtotal = useMemo(() => {
return cartItems.reduce((sum, item) => {
const effectivePrice = item.variant
? calculateEffectivePrice(item.variant)
: calculateEffectivePrice(item.product);
return sum + effectivePrice * item.quantity;
}, 0);
}, [cartItems]);
const shippingCost = useMemo(() => {
return calculateShippingCost(cartItems, deliveryLocation);
}, [cartItems, deliveryLocation]);
const discountAmount = useMemo(() => {
if (!discount) return 0;
return calculateDiscountAmount(subtotal, discount);
}, [subtotal, discount]);
const total = subtotal + shippingCost - discountAmount;
return {
subtotal,
shippingCost,
discountAmount,
total,
};
}
function calculateEffectivePrice(item: Product | ProductVariant): number {
// Extract price calculation logic
}
function calculateShippingCost(items: CartItem[], location: string): number {
// Extract shipping calculation
}
function calculateDiscountAmount(subtotal: number, discount: Discount): number {
// Extract discount calculation
}
// src/components/checkout/CheckoutCustomerInfo.tsx (~80 lines)
import { UseFormReturn } from 'react-hook-form';
import { CheckoutFormData } from '@/lib/validation/checkout-validation';
interface CheckoutCustomerInfoProps {
form: UseFormReturn(CheckoutFormData);
}
export function CheckoutCustomerInfo({ form }: CheckoutCustomerInfoProps) {
return (
(Card)
(CardHeader)
(CardTitle)Customer Information(/CardTitle)
(/CardHeader)
(CardContent)
{/* Focused component - just customer fields */}
(div className="space-y-4")
(div)
(Label htmlFor="email")Email(/Label)
(Input {...form.register('email')} /)
{form.formState.errors.email && (
<p className="text-sm text-red-600">{form.formState.errors.email.message}</p>
)}
(/div)
{/* ... other customer fields */}
(/div)
(/CardContent)
(/Card)
);
}
// src/components/checkout/CheckoutOrderSummary.tsx (~150 lines)
import { useCheckoutCalculations } from '@/hooks/useCheckoutCalculations';
interface CheckoutOrderSummaryProps {
cartItems: CartItem[];
discount: Discount | null;
deliveryLocation: string;
onApplyDiscount: (code: string) => Promise(void);
onRemoveDiscount: () => void;
}
export function CheckoutOrderSummary({
cartItems,
discount,
deliveryLocation,
onApplyDiscount,
onRemoveDiscount,
}: CheckoutOrderSummaryProps) {
const { subtotal, shippingCost, discountAmount, total } = useCheckoutCalculations(
cartItems,
discount,
deliveryLocation
);
return (
(Card)
(CardHeader)
(CardTitle)Order Summary(/CardTitle)
(/CardHeader)
(CardContent)
{/* Focused component - just summary and calculations */}
(div className="space-y-2")
(div className="flex justify-between")
(span)Subtotal(/span)
(span)${subtotal.toFixed(2)}(/span)
(/div)
{/* ... rest of summary */}
(/div)
(/CardContent)
(/Card)
);
}
// src/app/store/[slug]/checkout/page.tsx (~180 lines - much cleaner!)
"use client";
import { useCheckoutCalculations } from '@/hooks/useCheckoutCalculations';
import { checkoutSchema, CheckoutFormData } from '@/lib/validation/checkout-validation';
import { CheckoutCustomerInfo } from '@/components/checkout/CheckoutCustomerInfo';
import { CheckoutShippingAddress } from '@/components/checkout/CheckoutShippingAddress';
import { CheckoutOrderSummary } from '@/components/checkout/CheckoutOrderSummary';
import { CheckoutActions } from '@/components/checkout/CheckoutActions';
export default function CheckoutPage() {
const router = useRouter();
const params = useParams();
const { items: cartItems, clearCart } = useCart();
const [discount, setDiscount] = useState(Discount | null)(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const form = useForm(CheckoutFormData)({
resolver: zodResolver(checkoutSchema),
defaultValues: { /* ... */ },
});
const { subtotal, shippingCost, discountAmount, total } = useCheckoutCalculations(
cartItems,
discount,
form.watch('deliveryLocation')
);
const handleApplyDiscount = async (code: string) => {
// Simplified discount logic
};
const onSubmit = async (data: CheckoutFormData) => {
// Simplified order creation
};
return (
(div className="container max-w-6xl py-8")
(form onSubmit={form.handleSubmit(onSubmit)} className="grid gap-8 lg:grid-cols-3")
(div className="lg:col-span-2 space-y-6")
(CheckoutCustomerInfo form={form} /)
(CheckoutShippingAddress form={form} /)
(/div)
(div className="lg:col-span-1")
(CheckoutOrderSummary
cartItems={cartItems}
discount={discount}
deliveryLocation={form.watch('deliveryLocation')}
onApplyDiscount={handleApplyDiscount}
onRemoveDiscount={() =) setDiscount(null)}
/>
(CheckoutActions
isSubmitting={isSubmitting}
isValid={form.formState.isValid}
total={total}
/)
(/div)
(/form)
(/div)
);
}Impact Assessment
- Effort: High - Estimated 2-3 days to extract and test
- Risk: Medium - Requires careful testing of business logic extraction
- Benefit: Very High - One of the largest, most complex components in the codebase
- Priority: High - Critical user-facing feature that needs to be maintainable
Related Files
src/app/store/[slug]/checkout/page.tsx(1039 lines - to be refactored)src/lib/stores/cart-store.ts(cart state management)src/components/shipping/pathao-address-selector.tsx(Pathao integration)src/lib/services/checkout.service.ts(may exist for order processing)
Testing Strategy
-
Unit tests for business logic
- Test
calculateEffectivePrice()with various discount scenarios - Test
calculateShippingCost()with different locations - Test
calculateDiscountAmount()with percentage and fixed discounts - Test validation functions independently
- Test
-
Integration tests for hooks
- Test
useCheckoutCalculationswith various cart states - Verify calculations update correctly when dependencies change
- Test
-
Component tests
- Test each extracted component in isolation
- Verify form validation and error messages
- Test Pathao address selector integration
-
E2E tests
- Test complete checkout flow
- Verify order creation with different payment methods
- Test discount code application
AI generated by Daily Codebase Analyzer - Semantic Function Extraction & Refactoring
- expires on Feb 27, 2026, 8:37 PM UTC
Reactions are currently unavailable
Metadata
Metadata
Assignees
Type
Projects
Status
Backlog