import { __, sprintf } from '@wordpress/i18n'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Link } from 'react-router-dom'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { BarChart3, CheckCircle2, CreditCard, Mail, Package, Search, ShoppingCart, X, } from 'lucide-react'; import { Alert, Button, Page, Spinner } from './UI/index.jsx'; import { UISwitch } from './UI/switch.jsx'; import { api } from '../api/index.js'; const MODULES = [ { id: 'side-cart', key: 'arrayfunnel_side_cart_enabled', title: __( 'Side Cart', 'arrayfunnel' ), description: __( 'Let customers review their cart and continue shopping with a slide-out cart drawer.', 'arrayfunnel' ), icon: ShoppingCart, path: '/side-cart', }, { id: 'checkout', key: 'arrayfunnel_checkout_optimized', title: __( 'Checkout', 'arrayfunnel' ), description: __( 'Refine your checkout with connection badges and an optional countdown timer.', 'arrayfunnel' ), icon: CreditCard, path: '/checkout', }, { id: 'order-bumps', key: 'arrayfunnel_order_bumps_enabled', title: __( 'Order Bumps', 'arrayfunnel' ), description: __( 'Offer relevant add-ons at checkout with targeted offers and customizable designs.', 'arrayfunnel' ), icon: Package, path: '/order-bumps', }, { id: 'abandoned-cart', title: __( 'Abandoned Cart', 'arrayfunnel' ), description: __( 'Track abandoned carts and manage recovery emails, reminder timing, and recovery coupons.', 'arrayfunnel' ), icon: Mail, path: '/abandoned-cart', }, { id: 'analytics', title: __( 'Analytics', 'arrayfunnel' ), description: __( 'Follow revenue, orders, checkout conversion, and cart recovery from Home.', 'arrayfunnel' ), icon: BarChart3, path: '/', }, ]; const toModuleSettings = ( data ) => ( { arrayfunnel_side_cart_enabled: data.arrayfunnel_side_cart_enabled === 'yes' ? 'yes' : 'no', arrayfunnel_checkout_optimized: data.arrayfunnel_checkout_optimized === 'yes' ? 'yes' : 'no', arrayfunnel_order_bumps_enabled: data.arrayfunnel_order_bumps_enabled === 'no' ? 'no' : 'yes', } ); export default function Modules() { const pageHeader = { title: __( 'Modules', 'arrayfunnel' ), subtitle: __( 'Choose which ArrayFunnel modules run on your store.', 'arrayfunnel' ), }; const queryClient = useQueryClient(); const [ settings, setSettings ] = useState( null ); const [ original, setOriginal ] = useState( null ); const [ search, setSearch ] = useState( '' ); const { data, isLoading, error } = useQuery( { queryKey: [ 'settings' ], queryFn: api.getSettings, staleTime: 30_000, } ); useEffect( () => { if ( data ) { const values = toModuleSettings( data ); setSettings( values ); setOriginal( values ); } }, [ data ] ); const { mutateAsync: save, isPending } = useMutation( { mutationFn: api.saveSettings, onSuccess( response, submitted ) { queryClient.setQueryData( [ 'settings' ], ( current ) => ( { ...current, ...submitted, ...( response?.saved || {} ), } ) ); }, } ); const hasChanges = settings && JSON.stringify( settings ) !== JSON.stringify( original ); useEffect( () => { window.dispatchEvent( new window.CustomEvent( 'arrayfunnel:dirty-state', { detail: { dirty: Boolean( hasChanges ) }, } ) ); return () => window.dispatchEvent( new window.CustomEvent( 'arrayfunnel:dirty-state', { detail: { dirty: false }, } ) ); }, [ hasChanges ] ); useEffect( () => { const handleDiscard = () => { if ( ! isPending && original !== null ) { setSettings( { ...original } ); } }; window.addEventListener( 'arrayfunnel:discard-current-page', handleDiscard ); return () => window.removeEventListener( 'arrayfunnel:discard-current-page', handleDiscard ); }, [ isPending, original ] ); const saveCurrentPage = useCallback( async () => { if ( ! hasChanges || isPending ) { return; } const changed = Object.fromEntries( Object.entries( settings ).filter( ( [ key, value ] ) => value !== original[ key ] ) ); await save( changed ); }, [ hasChanges, isPending, original, save, settings ] ); useEffect( () => { const handleSave = ( event ) => { if ( hasChanges ) { event.detail?.register?.( saveCurrentPage() ); } }; window.addEventListener( 'arrayfunnel:save-current-page', handleSave ); return () => window.removeEventListener( 'arrayfunnel:save-current-page', handleSave ); }, [ hasChanges, saveCurrentPage ] ); const filteredModules = useMemo( () => { const terms = search .toLocaleLowerCase() .trim() .split( /\s+/ ) .filter( Boolean ); return MODULES.filter( ( module ) => terms.every( ( term ) => `${ module.title } ${ module.description }` .toLocaleLowerCase() .includes( term ) ) ); }, [ search ] ); const allEnabled = MODULES.filter( ( module ) => module.key ).every( ( module ) => settings?.[ module.key ] === 'yes' ); const set = ( key, value ) => setSettings( ( current ) => ( { ...current, [ key ]: value } ) ); if ( isLoading || ( ! settings && ! error ) ) { return (
); } if ( error ) { return ( { error.message } ); } return (
} > { search && (

{ sprintf( /* translators: %d: number of matching modules. */ __( '%d modules found', 'arrayfunnel' ), filteredModules.length ) }

) }
{ filteredModules.map( ( module ) => { const Icon = module.icon; const enabled = ! module.key || settings[ module.key ] === 'yes'; const savedEnabled = ! module.key || original[ module.key ] === 'yes'; const titleId = `arrayfunnel-module-${ module.id }`; return (

{ module.title }

{ ! module.key && ( { __( 'Core', 'arrayfunnel' ) } ) }

{ module.description }

{ module.key ? ( set( module.key, value ? 'yes' : 'no' ) } aria-labelledby={ titleId } aria-describedby={ `${ titleId }-description` } disabled={ isPending } /> ) : ( ) } { enabled && savedEnabled && ( { module.id === 'analytics' ? __( 'View Home', 'arrayfunnel' ) : __( 'Configure', 'arrayfunnel' ) } ) }
); } ) }
{ ! filteredModules.length && (
{ __( 'No modules found. Try a different name or keyword.', 'arrayfunnel' ) }
) }
); }