Your IP : 216.73.216.230


Current Path : /home/ephorei/www/wp-content/plugins/astra-sites/inc/lib/ai-builder/inc/assets/src/pages/
Upload File :
Current File : /home/ephorei/www/wp-content/plugins/astra-sites/inc/lib/ai-builder/inc/assets/src/pages/images.js

import {
	ArrowUpTrayIcon,
	CheckIcon,
	ChevronDownIcon,
	ChevronUpIcon,
	MagnifyingGlassIcon,
	SparklesIcon,
	XMarkIcon,
} from '@heroicons/react/24/outline';

import apiFetch from '@wordpress/api-fetch';
import { useDispatch, useSelect } from '@wordpress/data';
import { useCallback, useEffect, useRef, useState } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { uploadMedia } from '@wordpress/media-utils';

import { AnimatePresence } from 'framer-motion';
import { uniqBy } from 'lodash';
import { useDropzone } from 'react-dropzone';
import { useForm } from 'react-hook-form';
import toast from 'react-hot-toast';
import Masonry from 'react-layout-masonry';

import Dropdown from '../components/dropdown';
import Heading from '../components/heading';
import ImagePreview from '../components/image-preview';
import NavigationButtons from '../components/navigation-buttons';
import SuggestedKeywords from '../components/suggested-keywords';
import Tile from '../components/tile';
import UploadImage from '../components/upload-image';

import { classNames, toastBody } from '../helpers';
import { useDebounce, useDebounceWithCancel } from '../hooks/use-debounce';
import usePopper from '../hooks/use-popper';
import { useNavigateSteps } from '../router';
import { STORE_KEY } from '../store';
import { MB_IN_BYTE } from '../utils/constants';
import { clearSessionStorage, isValidImageURL } from '../utils/helpers';
import { USER_KEYWORD } from './select-template';

const ORIENTATIONS = {
	all: {
		value: 'all',
		label: __( 'All orientations', 'ai-builder' ),
	},
	landscape: {
		value: 'landscape',
		label: __( 'Landscape', 'ai-builder' ),
	},
	portrait: {
		value: 'portrait',
		label: __( 'Portrait', 'ai-builder' ),
	},
};

const TABS = [
	{
		label: __( 'Search Results', 'ai-builder' ),
		value: 'all',
	},
	{
		label: __( 'Upload Your Images', 'ai-builder' ),
		value: 'upload',
	},
	{
		label: __( 'Selected Images', 'ai-builder' ),
		value: 'selected',
	},
];

const IMAGES_PER_PAGE = 20;
const IMAGE_ENGINES = [ 'pexels' ];
const SKELETON_COUNT = 15;

const getImageSkeleton = ( count = SKELETON_COUNT ) => {
	const aspectRatioClassNames = [
		'aspect-[1/1]',
		'aspect-[1/2]',
		'aspect-[2/1]',
		'aspect-[2/2]',
		'aspect-[3/3]',
		'aspect-[4/3]',
		'aspect-[3/4]',
	];

	let aspectRatioIndex = 0;

	return Array.from( { length: count } ).map( ( _, index ) => {
		aspectRatioIndex =
			aspectRatioIndex === aspectRatioClassNames.length
				? 0
				: aspectRatioIndex;

		return (
			<Tile
				key={ `skeleton-${ index }` }
				className={ classNames(
					'relative overflow-hidden rounded-lg',
					'bg-slate-300 rounded-lg relative animate-pulse',
					aspectRatioClassNames[ aspectRatioIndex++ ]
				) }
			/>
		);
	} );
};

const Images = () => {
	const { nextStep, previousStep } = useNavigateSteps();
	const [ uploadingImagesCount, setUploadingImagesCount ] = useState( [ 0 ] );

	const { setWebsiteImagesAIStep, setWebsiteTemplateKeywords } =
		useDispatch( STORE_KEY );

	const [ uploadedImages, setUploadedImages ] = useState( [] );

	const uploadDroppedFiles = ( filesList ) => {
		setUploadedImages( [] );
		setUploadingImagesCount( filesList.length );
		filesList.forEach( async ( file ) => {
			try {
				await uploadMedia( {
					filesList: [ file ],
					onFileChange: ( files ) => {
						if ( ! files[ 0 ].id ) {
							return;
						}
						// if NOT a valid image name
						if ( ! isValidImageURL( files[ 0 ]?.url ) ) {
							toast.error(
								toastBody( {
									message: sprintf(
										/* translators: %s: file name */
										__(
											'Invalid file name! Please avoid special characters. (%s)',
											'ai-builder'
										),
										files[ 0 ].title
									),
								} )
							);
							setUploadingImagesCount( ( prev ) => prev - 1 );
							return;
						}
						setUploadedImages( ( prevState ) => [
							...prevState,
							...files,
						] );
						setUploadingImagesCount( ( prev ) => prev - 1 );
					},
				} );
			} catch ( error ) {
				console.error( error );
				toast.error(
					toastBody( {
						message: error.message.toString(),
					} )
				);
				setUploadingImagesCount( ( prevState ) => prevState - 1 );
			}
		} );
	};

	const onDropRejected = ( rejectedList ) => {
		if ( rejectedList.length > 20 ) {
			toast.error(
				toastBody( {
					message: __(
						`You can only upload 20 images at once`,
						'ai-builder'
					),
				} )
			);
			return;
		}
		rejectedList.forEach( ( { errors, file } ) => {
			toast.error(
				toastBody( {
					message: `${ errors[ 0 ].message } (${ file?.name })`,
				} )
			);
		} );
	};

	const { getRootProps, getInputProps } = useDropzone( {
		accept: {
			'image/png': [ '.png' ],
			'image/jpeg': [ '.jpeg', '.jpg' ],
		},
		noClick: true,
		noKeyboard: true,
		onDropAccepted: uploadDroppedFiles,
		maxFiles: 20,
		maxSize: 5 * MB_IN_BYTE,
		onDropRejected,
	} );

	const {
		stepsData: {
			businessName,
			selectedImages = [],
			keywords = [],
			businessType,
			businessDetails,
			businessContact,
			templateList,
			siteLanguage,
		},
		updateImages,
		loadingNextStep,
	} = useSelect( ( select ) => {
		const {
			getAIStepData,
			getAllPatternsCategories,
			getDynamicContent,
			getOnboardingAI,
			getLoadingNextStep,
		} = select( STORE_KEY );
		const onboardingAI = getOnboardingAI();
		return {
			stepsData: getAIStepData(),
			allPatternsCategories: getAllPatternsCategories(),
			dynamicContent: getDynamicContent(),
			isNewUser: onboardingAI?.isNewUser,
			updateImages: onboardingAI?.updateImages,
			loadingNextStep: getLoadingNextStep(),
		};
	} );

	useEffect( () => {
		setWebsiteImagesAIStep(
			uniqBy(
				[
					...selectedImages,
					...uploadedImages.map( ( image ) => ( {
						id: String( image.id ),
						url: image?.originalImageURL ?? image.url,
						optimized_url: image?.sizes?.large?.url ?? image.url,
						engine: '',
						description: '',
						orientation:
							image?.orientation ??
							( image?.width > image?.height
								? 'landscape'
								: 'portrait' ),
						author_name: image?.author_name ?? '',
						author_url: '',
					} ) ),
				],
				'id'
			)
		);
	}, [ uploadedImages.length ] );

	const [ orientation, setOrientation ] = useState( ORIENTATIONS.all );
	const [ keyword, setKeyword ] = useState(
		keywords?.length > 0 ? keywords[ 0 ] : ''
	);
	const [ images, setImages ] = useState( [] );
	const [ page, setPage ] = useState( 1 );
	const [ hasMore, setHasMore ] = useState( true );
	const [ isLoading, setIsLoading ] = useState( false );
	const [ backToTop, setBackToTop ] = useState( false );
	const [ activeTab, setActiveTab ] = useState( 'all' );

	const [ openSuggestedKeywords, setOpenSuggestedKeywords ] =
		useState( false );
	const [ referenceRef, popperRef ] = usePopper( {
		placement: 'bottom',
		modifiers: [ { name: 'offset', options: { offset: [ 0, 0 ] } } ],
	} );

	const mainWrapper = useRef( null );
	const scrollContainerRef = useRef( null );
	const imageRequestCompleted = useRef( false );
	const blackListedEngines = useRef( new Set() );
	const previouslySelected = useRef( selectedImages );
	const uploadImagesBtn = useRef( null );

	const { register, handleSubmit, setValue, reset, setFocus, watch } =
		useForm( { defaultValues: { keyword } } );
	const watchedKeyword = watch( 'keyword' );

	const [ debouncedImageKeywords, cancelDebouncedImageKeywords ] =
		useDebounceWithCancel( keyword, 500 );
	const debouncedOrientation = useDebounce( orientation, 500 );

	const handleOrientationChange = ( orientation_value ) => () => {
		setOrientation( orientation_value );
	};

	const handleSelectKeyword = ( keyword_value ) => {
		cancelDebouncedImageKeywords();
		setKeyword( keyword_value );
		setValue( 'keyword', keyword_value );
		setOpenSuggestedKeywords( false );
	};

	const getSuggestedKeywords = () => {
		return [ ...new Set( keywords ) ].filter( ( keywordItem ) => {
			if ( keyword.trim() === '' ) {
				return true;
			}
			return keywordItem?.toLowerCase() !== keyword?.toLowerCase();
		} );
	};

	const isSelected = ( image ) => {
		const imageIndx = selectedImages?.findIndex(
			( img ) => img.id === image.id
		);
		return imageIndx > -1;
	};

	// Function to merge new images with old images without duplicates
	const mergeUniqueImages = ( oldImages, newImages ) => {
		const uniqueImagesMap = new Map();

		[ ...oldImages, ...newImages ].forEach( ( image ) => {
			if ( ! uniqueImagesMap.has( image.id ) ) {
				// Add check to prevent overwrite
				uniqueImagesMap.set( image.id, image );
			}
		} );

		return Array.from( uniqueImagesMap.values() );
	};

	const handleImageSelection = useCallback(
		( image ) => {
			let newSelectedImages = [];

			if ( isSelected( image ) ) {
				image.id = String( image.id );
				newSelectedImages = selectedImages?.filter(
					( img ) => img.id !== image.id
				);
			} else {
				newSelectedImages = [ ...selectedImages, image ];
			}

			setWebsiteImagesAIStep( newSelectedImages );
		},
		[ selectedImages, setWebsiteImagesAIStep ] // eslint-disable-line
	);

	const handleClearImageSelection = useCallback(
		( event ) => {
			event.preventDefault();
			event.stopPropagation();

			setWebsiteImagesAIStep( [] );
		},
		[ setWebsiteImagesAIStep ]
	);

	const handleClickBackToTop = () => {
		if ( ! scrollContainerRef.current ) {
			return;
		}
		setBackToTop( false );
		scrollContainerRef.current.scrollTo( {
			top: 0,
			behavior: 'smooth',
		} );
		mainWrapper.current.scrollTo( {
			top: 0,
			behavior: 'smooth',
		} );
	};

	const handleShowBackToTop = ( event ) => {
		if ( ! event ) {
			return;
		}
		const { scrollTop } = event.target;
		const { scrollTop: mainScrollTop, scrollHeight: mainScrollHeight } =
			mainWrapper.current;
		const SCROLL_THRESHOLD = 50;
		if ( scrollTop > SCROLL_THRESHOLD && ! backToTop ) {
			setBackToTop( true );
			mainWrapper.current.scrollTo( {
				top: mainWrapper.current.scrollHeight,
				behavior: 'smooth',
			} );
		}
		if ( scrollTop <= SCROLL_THRESHOLD && backToTop ) {
			setBackToTop( false );
			mainWrapper.current.scrollTo( {
				top: 0,
				behavior: 'smooth',
			} );
		}
		if (
			scrollTop > SCROLL_THRESHOLD &&
			mainScrollTop < mainScrollHeight
		) {
			mainWrapper.current.scrollTo( {
				top: mainWrapper.current.scrollHeight,
				behavior: 'smooth',
			} );
		}
	};

	const handleScroll = ( event ) => {
		if ( ! event ) {
			return;
		}
		handleShowBackToTop( event );

		if ( activeTab === TABS[ 2 ].value ) {
			return;
		}

		if ( ! hasMore || isLoading ) {
			return;
		}

		const { scrollTop, scrollHeight, clientHeight } =
			scrollContainerRef.current;

		// Load more images when user is 200px away from the bottom
		if ( scrollTop + clientHeight >= scrollHeight - 100 ) {
			setPage( ( prev ) => prev + 1 );
		}
	};

	// Define a function to fetch all images
	const fetchAllImages = async ( engine ) => {
		// eslint-disable-line
		let searchKeywords = keyword;

		// If we the input filed is empty we are passing the keyword as businessName[category]
		if (
			typeof keyword === 'string' &&
			( ! keyword || keyword.trim() === '' )
		) {
			searchKeywords = businessName;
		}

		const payload = {
			keywords: searchKeywords,
			orientation: orientation.value,
			per_page: IMAGES_PER_PAGE?.toString(),
			page: page?.toString(),
		};
		try {
			const res = await apiFetch( {
				path: `zipwp/v1/images`,
				data: { ...payload, engine },
				method: 'POST',
				headers: {
					'X-WP-Nonce': aiBuilderVars.rest_api_nonce,
				},
			} );
			const imageResponse = res.data?.data || [];
			if ( ! res?.success ) {
				throw new Error( res?.data?.data );
			}
			// If there are no images, blacklist the engine
			if ( imageResponse?.length === 0 ) {
				blackListedEngines.current.add( engine );
			}

			// Filter out images that are already selected
			const newImages =
				imageResponse?.length > 0
					? imageResponse.map( ( image ) => ( {
							...image,
							id: String( image.id ),
					  } ) )
					: [];

			// Combine with existing images
			setImages( ( prevImages ) =>
				mergeUniqueImages( prevImages, newImages )
			);

			// Return image response length
			return imageResponse?.length || 0;
		} catch ( error ) {
			if ( error.name === 'AbortError' ) {
				throw error;
			}
			toast.error( toastBody( error ) );
		}

		return 0;
	};

	const getTemplates = async () => {
		try {
			const response = await apiFetch( {
				path: 'zipwp/v1/template-keywords',
				method: 'POST',
				headers: {
					'X-WP-Nonce': aiBuilderVars.rest_api_nonce,
				},
				data: {
					business_name: businessName,
					business_description: businessDetails,
					business_category: businessType,
					business_category_name: businessType,
				},
			} );

			if ( response.success ) {
				const templateKeywords = response?.data?.data ?? [];
				setWebsiteTemplateKeywords( [
					...new Set( templateKeywords ),
				] );
			} else {
				throw new Error( response?.data?.data );
			}
		} catch ( error ) {
			toast.error( toastBody( error ) );
		}
	};

	useEffect( () => {
		imageRequestCompleted.current = false;
		const fetchAllImagesFromAllEngines = async () => {
			if ( isLoading || ! hasMore ) {
				return;
			}
			try {
				setIsLoading( true );
				const responseLengths = [];
				for ( const engine of IMAGE_ENGINES ) {
					if ( ! blackListedEngines.current.has( engine ) ) {
						const response = await fetchAllImages( engine );
						responseLengths.push( response );
					}
				}

				if (
					Math.max( responseLengths.filter( Boolean ) ) <
					IMAGES_PER_PAGE
				) {
					setHasMore( false );
				} else {
					setHasMore( true );
				}
			} catch ( error ) {
				// Do nothing
				if ( error.name === 'AbortError' ) {
					return;
				}
			} finally {
				imageRequestCompleted.current = true;
				setIsLoading( false );
			}
		};

		fetchAllImagesFromAllEngines();
	}, [ debouncedImageKeywords, debouncedOrientation, page ] );

	useEffect( () => {
		imageRequestCompleted.current = false;
		blackListedEngines.current.clear();
		setPage( 1 );
		setImages( [] );
	}, [ keyword, orientation ] );

	// Trigger to load more images.
	useEffect( () => {
		mainWrapper.current = document.getElementById(
			'sp-onboarding-content-wrapper'
		);
		const mainWrapperElem = mainWrapper.current;
		if (
			!! mainWrapperElem &&
			! mainWrapperElem.classList.contains( 'hide-scrollbar' )
		) {
			mainWrapperElem.classList.add( 'hide-scrollbar' );
		}

		return () => {
			if (
				!! mainWrapperElem &&
				mainWrapperElem.classList.contains( 'hide-scrollbar' )
			) {
				mainWrapperElem.classList.remove( 'hide-scrollbar' );
			}
		};
	}, [] );

	useEffect( () => {
		if ( ! templateList?.length ) {
			getTemplates();
		}
	}, [ templateList ] );

	useEffect( () => {
		setFocus( 'keyword' );
	}, [] );

	const getUploadedImages = ( imagesArray = [] ) => {
		return imagesArray.filter( ( image ) =>
			IMAGE_ENGINES.some(
				( engine ) =>
					engine !== image.engine && image.engine !== 'placeholder'
			)
		);
	};

	const getSelectedImages = ( imagesArray = [] ) => {
		return imagesArray.filter( ( image ) =>
			IMAGE_ENGINES.some( ( engine ) => engine === image.engine )
		);
	};

	const getUploadingImageSkeleon = () => {
		if ( ! uploadingImagesCount ) {
			return [];
		}
		return getImageSkeleton( uploadingImagesCount, [ 'aspect-[1/1]' ] );
	};

	const getRenderItems = () => {
		switch ( activeTab ) {
			case TABS[ 0 ].value:
				return isLoading || ! imageRequestCompleted.current
					? [ ...images, ...getImageSkeleton() ]
					: images;
			case TABS[ 1 ].value:
				return [
					...getUploadedImages( selectedImages ),
					...getUploadingImageSkeleon(),
				];
			case TABS[ 2 ].value:
				return getSelectedImages( selectedImages );
			default:
				return isLoading
					? [ ...images, ...getImageSkeleton() ]
					: images;
		}
	};

	const renderImages = getRenderItems();

	const handleSaveDetails = async (
		selImages = selectedImages,
		skip = false
	) => {
		await apiFetch( {
			path: 'zipwp/v1/user-details',
			method: 'POST',
			headers: {
				'X-WP-Nonce': aiBuilderVars.rest_api_nonce,
			},
			data: {
				business_description: businessDetails,
				business_name: businessName,
				business_category: businessType,
				site_language: siteLanguage,
				images: skip ? [] : selImages,
				keywords,
				business_address: businessContact?.address || '',
				business_phone: businessContact?.phone || '',
				business_email: businessContact?.email || '',
				social_profiles: businessContact?.socialMedia || [],
			},
		} )
			.then( () => {} )
			.catch( () => {
				// Do nothing
			} );
	};

	const handleClickNext =
		( skip = false ) =>
		async () => {
			await handleSaveDetails( selectedImages, skip );
			clearSessionStorage( USER_KEYWORD );
			nextStep();
			if ( skip ) {
				setWebsiteImagesAIStep( previouslySelected.current ?? [] );
			}
		};

	const handleImageSearch = ( data ) => {
		setKeyword( data.keyword );
	};

	const handleClearSearch = () => {
		if ( ! watchedKeyword ) {
			return;
		}
		setKeyword( '' );
		reset( { keyword: '' } );
		setTimeout( () => {
			setFocus( 'keyword' );
		}, 10 );
	};

	const handleClickOutside = ( event ) => {
		const businessTypesWrapper = document.getElementById(
			'search-images-wrapper'
		);
		if (
			businessTypesWrapper &&
			! businessTypesWrapper.contains( event.target )
		) {
			setOpenSuggestedKeywords( false );
		}
	};

	// handle outside click to close the suggestions.
	useEffect( () => {
		document.addEventListener( 'mousedown', handleClickOutside );
		return () =>
			document.removeEventListener( 'mousedown', handleClickOutside );
	}, [ handleClickOutside ] );

	const handleOpenSuggestedKeywords = ( event ) => {
		if ( openSuggestedKeywords ) {
			return;
		}

		// Check if the event type is on click
		if ( event?.type === 'click' || event?.type === 'keydown' ) {
			setOpenSuggestedKeywords( true );
		}
	};

	return (
		<div
			className="w-full flex flex-col flex-auto h-full overflow-y-auto"
			ref={ scrollContainerRef }
			onScroll={ handleScroll }
		>
			<div className="w-full space-y-6">
				<Heading
					heading={ __( 'Select the Images', 'ai-builder' ) }
					className="px-5 md:px-10 lg:px-14 xl:px-15 pt-5 md:pt-10 lg:pt-8 xl:pt-8 max-w-fit mx-auto"
				/>
				<form
					className="w-full overflow-visible min-h-[3.125rem]"
					onSubmit={ handleSubmit( handleImageSearch ) }
					data-disabled={ loadingNextStep }
				>
					<div
						id="search-images-wrapper"
						ref={ referenceRef }
						className={ classNames(
							'relative w-full max-w-[37.5rem] mx-auto pl-4 pr-12 py-3 border border-button-disabled rounded-md shadow bg-white z-[2]',
							{
								'pb-0 rounded-b-none border-b-0 shadow-md':
									openSuggestedKeywords,
								'focus-within:ring-1 focus-within:ring-accent-st focus-within:border-accent-st focus-within:outline-none':
									! openSuggestedKeywords,
							}
						) }
						onClick={ ( event ) => {
							// If event target is `search-images-wrapper` then focus input.
							if ( event.target.id !== 'search-images-wrapper' ) {
								return;
							}
							setFocus( 'keyword' );
							if ( openSuggestedKeywords ) {
								return;
							}
							setOpenSuggestedKeywords( true );
						} }
					>
						<div className="absolute top-[0.875rem] right-3 flex items-center">
							<button
								type="button"
								className="w-auto h-auto p-0 flex items-center justify-center cursor-pointer bg-transparent border-0 focus:outline-none"
								onClick={ handleClearSearch }
							>
								{ watchedKeyword ? (
									<XMarkIcon className="w-5 h-5 text-zip-app-inactive-icon" />
								) : (
									<MagnifyingGlassIcon className="w-5 h-5 text-zip-app-inactive-icon" />
								) }
							</button>
						</div>
						<input
							className="!text-sm p-0 border-0 w-full focus:outline-none focus:ring-0 focus-visible:outline-none"
							placeholder={ __(
								'Add more relevant keywords…',
								'ai-builder'
							) }
							autoComplete="off"
							onKeyDown={ handleOpenSuggestedKeywords }
							onClick={ handleOpenSuggestedKeywords }
							{ ...register( 'keyword' ) }
						/>
						<div
							ref={ popperRef }
							className={ classNames(
								'w-[calc(100%_+_2px)] px-3 pb-4 z-10 bg-white shadow-md border-x border-b border-t-0 border-solid border-border-tertiary rounded-b-md',
								{
									invisible: ! openSuggestedKeywords,
								}
							) }
						>
							{ openSuggestedKeywords && (
								<hr
									className="!mx-0 !my-3 border-t border-solid border-b-0 border-border-tertiary"
									tabIndex={ -1 }
								/>
							) }
							<h6 className="flex items-center justify-start gap-1.5 text-sm text-heading-text font-medium mb-4">
								<span>
									{ __( 'Suggested Keywords', 'ai-builder' ) }
								</span>
								<SparklesIcon className="inline-block size-4" />
							</h6>
							<SuggestedKeywords
								keywords={ getSuggestedKeywords() }
								onClick={ handleSelectKeyword }
								data-disabled={ loadingNextStep }
							/>
						</div>
					</div>
				</form>
			</div>
			<div className="sticky top-0 pt-4 space-y-6 z-[1] bg-container-background px-5 md:px-10 lg:px-14 xl:px-15">
				<div className=" rounded-t-lg py-4">
					<div className="flex items-center justify-between">
						<div className="flex items-center gap-1 text-sm font-normal leading-[21px]">
							{ /* Tabs */ }
							<div className="flex items-center justify-start gap-3">
								{ TABS.map( ( tab ) => (
									<button
										className={ classNames(
											'before:content-[attr(data-title)] before:block before:font-bold before:text-sm before:invisible before:h-0',
											'pb-3 px-0 pt-0 !border-x-0 !border-t-0 border-b-2 border-solid !border-b-accent-st bg-transparent text-sm font-semibold text-accent-st cursor-pointer focus-visible:outline-none focus:outline-none',
											tab.value !== activeTab &&
												'border-0 font-normal text-body-text'
										) }
										key={ tab.value }
										type="button"
										onClick={ () =>
											setActiveTab( tab.value )
										}
										data-title={ tab.label }
										disabled={ loadingNextStep }
									>
										{ tab.label }
										{ tab.value === TABS[ 2 ].value &&
											!! getSelectedImages(
												selectedImages
											)?.length &&
											` (${
												getSelectedImages(
													selectedImages
												)?.length
											})` }
										{ tab.value === TABS[ 1 ].value &&
											!! getUploadedImages(
												selectedImages
											)?.length &&
											` (${
												getUploadedImages(
													selectedImages
												)?.length
											})` }
									</button>
								) ) }
							</div>
						</div>
						{ activeTab === TABS[ 0 ].value && (
							<Dropdown
								placement="right"
								trigger={
									<div
										className="flex items-center gap-2 min-w-[100px] py-3 pl-4 pr-3 cursor-pointer border border-border-primary rounded-md"
										data-disabled={ loadingNextStep }
									>
										<span className="text-sm font-normal text-body-text leading-[150%]">
											{ orientation.label }
										</span>
										<ChevronDownIcon className="w-5 h-5 text-app-inactive-icon" />
									</div>
								}
								align="top"
								width="48"
								contentClassName="p-1 bg-white"
								disabled={ loadingNextStep }
							>
								{ Object.values( ORIENTATIONS ).map(
									( orientationItem, index ) => (
										<Dropdown.Item
											as="div"
											key={ index }
											className="only:!p-0"
										>
											<button
												type="button"
												className="w-full flex items-center justify-between gap-2 py-1.5 px-2 text-sm font-normal leading-5 text-body-text hover:bg-background-secondary transition duration-150 ease-in-out space-x-2 rounded bg-white border-none cursor-pointer"
												onClick={ handleOrientationChange(
													orientationItem
												) }
											>
												<span>
													{ orientationItem.label }
												</span>
												{ orientationItem.value ===
													orientation.value && (
													<CheckIcon className="w-4 h-4 text-heading-text" />
												) }
											</button>
										</Dropdown.Item>
									)
								) }
							</Dropdown>
						) }
						{ activeTab === TABS[ 2 ].value &&
							!! selectedImages?.length && (
								<button
									onClick={ handleClearImageSelection }
									className="px-1 py-px bg-transparent border border-solid border-border-primary rounded text-xs leading-4 text-body-text cursor-pointer"
									disabled={ loadingNextStep }
								>
									{ __( 'Clear', 'ai-builder' ) }
								</button>
							) }
						{ activeTab === TABS[ 1 ].value && (
							<UploadImage
								render={ ( { open } ) => (
									<button
										ref={ uploadImagesBtn }
										className="px-0 bg-transparent border-none rounded text-xs leading-5 font-semibold text-accent-st cursor-pointer inline-flex items-center justify-end gap-2"
										onClick={ open }
										disabled={ loadingNextStep }
									>
										<ArrowUpTrayIcon
											className="w-4 h-4 text-zip-app-inactive-icon"
											strokeWidth={ 2 }
										/>
										<span>
											{ __(
												'Upload Your Images',
												'ai-builder'
											) }
										</span>
									</button>
								) }
							/>
						) }
					</div>
				</div>
			</div>
			<div
				className="rounded-b-lg py-4 flex flex-col flex-auto relative px-5 md:px-10 lg:px-14 xl:px-15"
				data-disabled={ loadingNextStep }
			>
				{ activeTab === TABS[ 1 ].value && ! renderImages.length && (
					<div
						className={ classNames(
							'relative flex flex-col items-center justify-center gap-3 py-[3.125rem] px-4 bg-background-primary border border-dashed border-border-tertiary rounded cursor-pointer'
						) }
						data-disabled={ loadingNextStep }
						{ ...getRootProps() }
					>
						<input { ...getInputProps() } />
						<ArrowUpTrayIcon className="w-6 h-6 text-zip-app-inactive-icon" />
						<p className="text-zip-body-text text-base">
							<span className="text-accent-st min-w-fit break-keep text-nowrap whitespace-nowrap font-semibold mr-1">
								{ __( 'Upload images', 'ai-builder' ) }
							</span>
							{ __(
								'or drop your images here (Max 20)',
								'ai-builder'
							) }
						</p>
						<p className="text-zip-body-text text-base">
							{ __( 'PNG, JPG, JPEG', 'ai-builder' ) }
						</p>
						<p className="text-zip-body-text text-base">
							{ __( 'Max size: 5 MB per file', 'ai-builder' ) }
						</p>
						<div
							className="absolute inset-0"
							onClick={ () => {
								if ( ! uploadImagesBtn?.current ) {
									return;
								}
								uploadImagesBtn?.current.click();
							} }
						/>
					</div>
				) }

				<AnimatePresence>
					{ renderImages?.length > 0 && (
						<Masonry
							className="gap-6 [&>div]:gap-6"
							columns={ {
								default: 1,
								220: 1,
								767: 3,
								1024: 3,
								1280: 5,
								1441: 6,
								1920: 6,
							} }
						>
							{ renderImages.map( ( image ) =>
								image?.optimized_url ? (
									<ImagePreview
										key={ image.id }
										image={ image }
										isSelected={ isSelected( image ) }
										onClick={ handleImageSelection }
										variant={
											activeTab === TABS[ 2 ].value ||
											activeTab === TABS[ 1 ].value
												? 'selection'
												: 'default'
										}
									/>
								) : (
									image
								)
							) }
						</Masonry>
					) }
				</AnimatePresence>

				{ activeTab === TABS[ 2 ].value && ! renderImages.length && (
					<div className="flex flex-col items-center justify-center h-full">
						<p className="text-secondary-text text-center px-10 py-5 border-2 border-dashed border-border-primary rounded-md">
							{ __(
								'You have not selected any images yet.',
								'ai-builder'
							) }
						</p>
					</div>
				) }

				{ activeTab === TABS[ 0 ].value &&
					! isLoading &&
					! images.length &&
					imageRequestCompleted.current && (
						<div className="flex flex-col items-center justify-center h-full">
							<p className="text-secondary-text text-center px-10 py-5 border-2 border-dashed border-border-primary rounded-md">
								{ ! keyword.length ? (
									<>
										{ __(
											'Find the perfect images for your website by entering a keyword or selecting from the suggested options.',
											'ai-builder'
										) }
									</>
								) : (
									<>
										{ __(
											"We couldn't find anything with your keyword.",
											'ai-builder'
										) }
										<br />
										{ __(
											'Try to refine your search.',
											'ai-builder'
										) }
									</>
								) }
							</p>
						</div>
					) }
				{ activeTab === TABS[ 0 ].value &&
					! isLoading &&
					! hasMore &&
					!! images.length && (
						<div className="pb-5 pt-10 flex flex-col items-center justify-center h-full">
							<p className="text-secondary-text text-sm leading-5 text-center after:mx-2.5 after:content-[''] after:inline-block after:w-5 sm:after:w-12 after:h-px after:bg-app-border after:relative after:-top-[5px] before:mx-2.5 before:content-[''] before:inline-block before:w-5 sm:before:w-12 before:h-px before:bg-app-border before:relative before:-top-[5px]">
								{ __(
									'End of the search results',
									'ai-builder'
								) }
							</p>
						</div>
					) }
			</div>
			{ /* Back to the top */ }
			{ backToTop && (
				<div className="absolute right-20 bottom-28 ml-auto">
					<button
						type="button"
						className="absolute bottom-0 right-0 z-10 w-8 h-8 rounded-full bg-accent-st border-0 border-solid text-white flex items-center justify-center shadow-sm cursor-pointer"
						onClick={ handleClickBackToTop }
						disabled={ loadingNextStep }
					>
						<ChevronUpIcon className="w-5 h-5" />
					</button>
				</div>
			) }
			<div className="sticky bottom-0 bg-container-background py-4.75 px-5 md:px-10 lg:px-14 xl:px-15">
				<NavigationButtons
					{ ...( updateImages
						? {
								continueButtonText: __(
									'Save & Exit',
									'ai-builder'
								),
								onClickContinue: handleSaveDetails,
						  }
						: {
								onClickContinue: handleClickNext(),
								onClickSkip: handleClickNext( true ),
								onClickPrevious: previousStep,
						  } ) }
				/>
			</div>
		</div>
	);
};

export default Images;