/home/lafermj/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, getScreenWidthBreakPoint, 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' ),
mobileLabel: __( 'Upload', '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 [ breakpoint, setBreakpoint ] = useState(
getScreenWidthBreakPoint()
);
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 );
}
};
useEffect( () => {
const handleResize = () => {
setBreakpoint( getScreenWidthBreakPoint() );
};
window.addEventListener( 'resize', handleResize );
return () => {
window.removeEventListener( 'resize', handleResize );
};
}, [] );
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 px-5 md:px-10 lg:px-14 xl:px-15">
<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 sm:flex-row flex-col items-start sm:items-center justify-between">
<div className="flex items-center gap-1 text-sm font-normal leading-[21px] sm:mb-0 mb-5 w-full">
{ /* Tabs */ }
<div className="flex items-center justify-start gap-3">
{ TABS.map( ( tab ) => (
<button
className={ classNames(
'before:content-[""] 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.value === TABS[ 1 ].value &&
breakpoint === 'xs'
? tab.mobileLabel
: 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] w-[160px] 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="flex border px-2.5 py-2 font-semibold border-blue-crayola text-xs rounded text-blue-crayola bg-white w-24"
disabled={ loadingNextStep }
>
<XMarkIcon className="w-4 h-4 block mr-1 text-zip-body-text" />
{ __( 'Clear all', '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 w-auto sm:w-44"
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 text-center">
<span className="text-accent-st min-w-fit break-keep text-nowrap whitespace-nowrap font-semibold mr-1">
{ __( 'Upload images', 'ai-builder' ) }
</span>
<span>
{ __(
'or drop your images here',
'ai-builder'
) }
<br />
{ __( '(Max 20)', 'ai-builder' ) }
</span>
</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-4 sm:gap-6 [&>div]:gap-6"
columns={ {
default: 1,
640: 2,
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;