All files / src/components/features/cart CartMergeHandler.tsx

48.43% Statements 62/128
88.88% Branches 8/9
100% Functions 2/2
48.43% Lines 62/128

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 1291x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 11x 11x 11x 11x 11x 11x 11x 10x 2x 2x 2x 2x 2x 2x 2x                                                                                                                                     10x 10x 10x 10x 6x 6x 6x 6x 2x 2x 6x 11x 11x 11x 11x 10x 3x 3x 11x 11x 11x 11x 11x  
'use client';
 
import { useEffect, useRef } from 'react';
import { useSession } from 'next-auth/react';
import { useDispatch } from 'react-redux';
import {
  setIsAnonymous,
  fetchCart } from '@/redux/features/cartSlice';
import { getCartCookie, deleteCartCookie } from '@/lib/cart';
import { getOrCreateDeviceId } from '@/lib/device-id';
import { AppDispatch } from '@/redux/store';
import { clientLogger } from '@/lib/logging/clientLogger';
 
/**
 * CartMergeHandler
 *
 * Handles post-login cart merge ONLY:
 * 1. Detects when user logs in with items in anonymous cart
 * 2. Calls merge API endpoint to merge anonymous cart into user cart
 * 3. Clears anonymous cookie after successful merge
 *
 * Note: CartInitializer handles loading the cart based on auth state.
 * This component only handles the merge operation.
 */
export function CartMergeHandler() {
  const dispatch = useDispatch<AppDispatch>();
  const { status } = useSession();
  const hasMerged = useRef(false);
  const previousStatus = useRef<string | null>(null);
 
  useEffect(() => {
    const handleCartMerge = async () => {
      // Only proceed when status changes from unauthenticated to authenticated
      // (not from loading - that's handled by CartInitializer)
      if (
        status === 'authenticated' &&
        !hasMerged.current &&
        previousStatus.current === 'unauthenticated'
      ) {
        // Get anonymous cart from cookie
        const anonymousCart = getCartCookie();

        if (anonymousCart && anonymousCart.length > 0) {
          // Mark as merged to prevent duplicate calls
          hasMerged.current = true;

          const deviceId = getOrCreateDeviceId();

          try {
            // Call merge endpoint
            const response = await fetch('/api/cart/merge', {
              method: 'POST',
              headers: {
                'Content-Type': 'application/json',
                'x-device-id': deviceId},
              body: JSON.stringify({ anonymousItems: anonymousCart })});

            if (response.ok) {
              const result = await response.json();
              clientLogger.info('Cart merged successfully', result);

              // Clear anonymous cookie
              deleteCartCookie();

              // Refresh cart from database to get merged items
              // IMPORTANT: Await the fetchCart to ensure cart is loaded before continuing
              dispatch(setIsAnonymous(false));
              try {
                await dispatch(fetchCart()).unwrap();
                clientLogger.info('Cart fetched after merge', {});
              } catch (fetchError) {
                clientLogger.error('Failed to fetch cart after merge', fetchError instanceof Error ? fetchError : new Error(String(fetchError)));
              }

              // Optional: Show notification about merge results
              if (result.conflicts && result.conflicts > 0) {
                clientLogger.info(
                  `Cart merged with ${result.conflicts} quantity adjustment(s)`,
                  result.conflictDetails
                );
              }
            } else {
              const error = await response.json();
              clientLogger.error('Failed to merge cart', error);
              // On failure, still fetch user cart (merge will be retried on next login)
              dispatch(setIsAnonymous(false));
              try {
                await dispatch(fetchCart()).unwrap();
              } catch (fetchError) {
                clientLogger.error('Failed to fetch cart after merge failure', fetchError instanceof Error ? fetchError : new Error(String(fetchError)));
              }
            }
          } catch (error) {
            clientLogger.error('Cart merge error', error instanceof Error ? error : new Error(String(error)));
            // On error, still fetch user cart
            dispatch(setIsAnonymous(false));
            try {
              await dispatch(fetchCart()).unwrap();
            } catch (fetchError) {
              clientLogger.error('Failed to fetch cart after merge error', fetchError instanceof Error ? fetchError : new Error(String(fetchError)));
            }
          }
        }
        // If no anonymous cart, CartInitializer already handles loading user cart
      }
    };
 
    // Update previous status and handle merge
    if (status !== 'loading' && status !== previousStatus.current) {
      const oldStatus = previousStatus.current;
      previousStatus.current = status;
 
      if (oldStatus !== null) {
        handleCartMerge();
      }
    }
  }, [status, dispatch]);
 
  // Reset merge flag when user logs out
  useEffect(() => {
    if (status === 'unauthenticated') {
      hasMerged.current = false;
    }
  }, [status]);
 
  // This component doesn't render anything
  return null;
}