-
Notifications
You must be signed in to change notification settings - Fork 2
Description
Problem
The checkout page component (src/app/store/[slug]/checkout/page.tsx) is 829 lines and handles multiple concerns in a single component:
- Cart validation and management (items, pricing, courier calculations)
- Multi-step form logic (shipping, billing, payment selection)
- Payment method selection UI and availability checking
- Discount code validation and application
- Order creation and submission
- SSLCommerz payment initialization
- 6+ useState hooks and complex state management
- Multiple useEffect hooks for cart validation and sync
This creates several issues:
- Hard to test - Component has too many responsibilities
- Hard to maintain - Changes to one part risk breaking others
- Hard to reuse - Logic is tightly coupled to this specific page
- Poor performance - Large component re-renders more than necessary
- Difficult to understand - New developers face a steep learning curve
Current Code Location
- File:
src/app/store/[slug]/checkout/page.tsx - Lines: 829 (very large for a single component)
- Component:
CheckoutPage - Complexity: High (multiple concerns, 6+ hooks, complex state)
Proposed Refactoring
Break down the monolithic checkout page into smaller, focused components and custom hooks:
Components to Extract:
CheckoutCartSummary- Cart items display, pricing breakdown, courier chargesCheckoutShippingForm- Shipping address form fieldsCheckoutBillingForm- Billing address form (conditional rendering)CheckoutPaymentMethod- Payment method selection cardsCheckoutDiscountCode- Discount code input and validation
Hooks to Extract:
useCheckoutCart- Cart validation, item removal, price calculationsuseDiscountCode- Discount validation and application logicuseCheckoutSubmit- Order creation and payment initialization
Benefits
- Improved testability - Each component/hook can be tested in isolation
- Better reusability - Components can be used in other checkout-related flows
- Clearer separation of concerns - Each component has a single responsibility
- Easier maintenance - Changes are localized to specific components
- Better performance - Smaller components re-render only when their props change
- Improved developer experience - Easier to understand and modify
- Type safety - Smaller components have simpler, more explicit prop types
Suggested Approach
Phase 1: Extract Display Components (Low Risk)
- Create
CheckoutCartSummary.tsxfor cart display - Create
CheckoutPaymentMethod.tsxfor payment selection UI - Update main checkout page to use new components
Phase 2: Extract Form Components (Medium Risk)
4. Create CheckoutShippingForm.tsx with form fields
5. Create CheckoutBillingForm.tsx with conditional billing fields
6. Pass form control from parent via props
Phase 3: Extract Custom Hooks (Higher Risk)
7. Create hooks/useCheckoutCart.ts for cart logic
8. Create hooks/useDiscountCode.ts for discount logic
9. Create hooks/useCheckoutSubmit.ts for submission logic
10. Refactor main page to use hooks
Phase 4: Testing & Optimization
11. Add unit tests for each component
12. Add unit tests for each hook
13. Add integration test for complete checkout flow
14. Optimize re-render behavior with React.memo if needed
Code Example
Before (current monolithic structure - simplified):
// src/app/store/[slug]/checkout/page.tsx (829 lines)
export default function CheckoutPage() {
// 6+ useState hooks
const [isProcessing, setIsProcessing] = useState(false);
const [isValidating, setIsValidating] = useState(true);
const [subtotal, setSubtotal] = useState(0);
const [courierTotal, setCourierTotal] = useState(0);
const [discount, setDiscount] = useState(0);
const [discountCode, setDiscountCode] = useState("");
// Cart state
const items = useCart((state) => state.items);
const removeItem = useCart((state) => state.removeItem);
const getSubtotal = useCart((state) => state.getSubtotal);
// Complex validation useEffect
useEffect(() => {
const validateCart = async () => {
// 50+ lines of validation logic
};
validateCart();
}, [items, storeSlug]);
// Form handling
const { register, handleSubmit, formState: { errors }, watch } = useForm({
resolver: zodResolver(checkoutSchema),
});
// Payment method selection
const paymentMethod = watch("paymentMethod");
// Order submission (100+ lines)
const onSubmit = async (data: CheckoutFormData) => {
// Complex order creation logic
// Payment initialization
// Error handling
// Redirect logic
};
return (
(div)
{/* 400+ lines of JSX mixing all concerns */}
{/* Cart summary */}
{/* Shipping form */}
{/* Billing form */}
{/* Payment method selection */}
{/* Discount code input */}
{/* Order summary */}
{/* Submit button */}
(/div)
);
}After (decomposed structure):
// src/app/store/[slug]/checkout/page.tsx (reduced to ~200 lines)
import { CheckoutCartSummary } from "@/components/checkout/checkout-cart-summary";
import { CheckoutShippingForm } from "@/components/checkout/checkout-shipping-form";
import { CheckoutBillingForm } from "@/components/checkout/checkout-billing-form";
import { CheckoutPaymentMethod } from "@/components/checkout/checkout-payment-method";
import { CheckoutDiscountCode } from "@/components/checkout/checkout-discount-code";
import { useCheckoutCart } from "@/hooks/useCheckoutCart";
import { useDiscountCode } from "@/hooks/useDiscountCode";
import { useCheckoutSubmit } from "@/hooks/useCheckoutSubmit";
export default function CheckoutPage() {
const params = useParams<{ slug: string }>();
const { storeUrl, storeApiUrl } = useStoreUrl();
// Custom hooks handle complex logic
const {
items,
isValidating,
subtotal,
courierTotal,
handleRemoveItem,
} = useCheckoutCart(params.slug, storeApiUrl);
const {
discount,
discountCode,
isValidatingDiscount,
applyDiscountCode,
} = useDiscountCode(storeApiUrl);
const {
isProcessing,
submitOrder,
} = useCheckoutSubmit(params.slug, storeApiUrl);
// Form handling
const form = useForm({
resolver: zodResolver(checkoutSchema),
});
const billingSameAsShipping = form.watch("billingSameAsShipping");
const paymentMethod = form.watch("paymentMethod");
const onSubmit = async (data: CheckoutFormData) => {
await submitOrder(data, { subtotal, courierTotal, discount, items });
};
return (
(div className="container mx-auto px-4 py-8")
(div className="grid grid-cols-1 lg:grid-cols-2 gap-8")
{/* Left column: Forms */}
(div className="space-y-6")
(CheckoutShippingForm form={form} /)
{!billingSameAsShipping && (
(CheckoutBillingForm form={form} /)
)}
(CheckoutPaymentMethod
form={form}
availableMethods={PAYMENT_METHODS}
/)
(CheckoutDiscountCode
value={discountCode}
discount={discount}
isValidating={isValidatingDiscount}
onApply={applyDiscountCode}
/)
(/div)
{/* Right column: Cart summary */}
(div)
(CheckoutCartSummary
items={items}
subtotal={subtotal}
courierTotal={courierTotal}
discount={discount}
isValidating={isValidating}
onRemoveItem={handleRemoveItem}
/)
(Button
onClick={form.handleSubmit(onSubmit)}
disabled={isProcessing || isValidating}
className="w-full mt-6"
)
{isProcessing ? "Processing..." : `Pay \$\{formatCurrency(total)}`}
(/Button)
(/div)
(/div)
(/div)
);
}
// src/components/checkout/checkout-cart-summary.tsx (~100 lines)
interface CheckoutCartSummaryProps {
items: CartItem[];
subtotal: number;
courierTotal: number;
discount: number;
isValidating: boolean;
onRemoveItem: (itemId: string) => void;
}
export function CheckoutCartSummary({
items, subtotal, courierTotal, discount, isValidating, onRemoveItem
}: CheckoutCartSummaryProps) {
// Focused component with clear responsibilities
return (
(Card)
(CardHeader)
(CardTitle)Order Summary(/CardTitle)
(/CardHeader)
(CardContent)
{/* Cart items display */}
{/* Price breakdown */}
(/CardContent)
(/Card)
);
}
// src/hooks/useCheckoutCart.ts (~80 lines)
export function useCheckoutCart(storeSlug: string, storeApiUrl: Function) {
const [isValidating, setIsValidating] = useState(true);
const [subtotal, setSubtotal] = useState(0);
const [courierTotal, setCourierTotal] = useState(0);
const items = useCart((state) => state.items);
const removeItem = useCart((state) => state.removeItem);
const getSubtotal = useCart((state) => state.getSubtotal);
const getCourierTotal = useCart((state) => state.getCourierTotal);
// Validation logic extracted here
useEffect(() => {
const validateCart = async () => {
// Cart validation implementation
};
validateCart();
}, [items, storeSlug]);
return {
items,
isValidating,
subtotal,
courierTotal,
handleRemoveItem: removeItem,
};
}Impact Assessment
- Effort: High - Requires careful refactoring and comprehensive testing (~8-12 hours)
- Risk: Medium - Complex component with business-critical logic (requires extensive testing)
- Benefit: Very High - Dramatically improves maintainability, testability, and developer experience
- Priority: Medium - Important for long-term maintainability, but not urgent
Related Files
src/app/store/[slug]/checkout/page.tsx(primary file)- Create new:
src/components/checkout/checkout-cart-summary.tsxsrc/components/checkout/checkout-shipping-form.tsxsrc/components/checkout/checkout-billing-form.tsxsrc/components/checkout/checkout-payment-method.tsxsrc/components/checkout/checkout-discount-code.tsxsrc/hooks/useCheckoutCart.tssrc/hooks/useDiscountCode.tssrc/hooks/useCheckoutSubmit.ts
Testing Strategy
-
Before refactoring:
- Document current checkout flow behavior
- Create end-to-end test for complete checkout process
- Ensure existing functionality is fully tested
-
During refactoring (iterative approach):
- Test each extracted component in isolation
- Test each custom hook with comprehensive unit tests
- Maintain E2E test to catch regressions
-
Component tests:
- Test
CheckoutCartSummaryrendering and remove functionality - Test form components with valid/invalid inputs
- Test payment method selection and availability
- Test discount code validation and display
- Test
-
Hook tests:
- Test
useCheckoutCartcart validation logic - Test
useDiscountCodediscount application - Test
useCheckoutSubmitorder creation flow - Mock API calls appropriately
- Test
-
Integration tests:
- Test complete checkout flow from cart to order creation
- Test form validation across all steps
- Test payment method switching
- Test discount code application during checkout
-
Manual testing:
- Complete checkout with Cash on Delivery
- Complete checkout with SSLCommerz payment
- Test with discount codes
- Test form validation errors
- Test cart validation and invalid item removal
- Test on mobile and desktop viewports
AI generated by Daily Codebase Analyzer - Semantic Function Extraction & Refactoring
- expires on Mar 6, 2026, 1:56 PM UTC
Metadata
Metadata
Assignees
Type
Projects
Status