import { __, sprintf } from '@wordpress/i18n'; import React, { useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Plus, Search, Sparkles, Trash2 } from 'lucide-react'; import { Alert, Button, Card, Input, Page, Select, Spinner, Toggle, } from './UI/index.jsx'; import { api } from '../api/index.js'; import { decodeHtmlEntities } from '../lib/utils.js'; const TEMPLATES = [ { value: 'classic', label: __( 'Classic (bordered card)', 'arrayfunnel' ), }, { value: 'highlight', label: __( 'Highlight (colored strip)', 'arrayfunnel' ), }, { value: 'minimal', label: __( 'Minimal (clean inline)', 'arrayfunnel' ), }, ]; const CONDITIONS = [ { value: 'all', label: __( 'Show on all orders', 'arrayfunnel' ), }, { value: 'product', label: __( 'Specific products in cart', 'arrayfunnel' ), }, { value: 'category', label: __( 'Specific category in cart', 'arrayfunnel' ), }, { value: 'cart_total', label: __( 'Specific cart total range', 'arrayfunnel' ), }, ]; export default function OrderBumpManager() { const qc = useQueryClient(); const [ editing, setEditing ] = useState( null ); const [ deleting, setDeleting ] = useState( null ); const { data, isLoading } = useQuery( { queryKey: [ 'bumps' ], queryFn: api.getBumps, staleTime: 45_000, gcTime: 10 * 60_000, } ); const { mutate: deleteBump, isPending: deletingNow } = useMutation( { mutationFn: ( id ) => api.deleteBump( id ), onSuccess() { qc.invalidateQueries( [ 'bumps' ] ); setDeleting( null ); }, } ); const bumps = data?.items || []; if ( editing !== null ) { return ( setEditing( null ) } /> ); } return ( setEditing( {} ) }> { __( 'New Bump', 'arrayfunnel' ) } } > { isLoading ? (
) : bumps.length === 0 ? (

{ __( 'No Order Bumps Yet', 'arrayfunnel' ) }

{ __( 'Create your first bump and start lifting average order value.', 'arrayfunnel' ) }

) : (
{ bumps.map( ( bump ) => (
{ bump.product_image ? ( ) : (
{ __( '?', 'arrayfunnel' ) }
) }

{ bump.title || __( 'Untitled Bump', 'arrayfunnel' ) }

{ bump.status ? __( 'Active', 'arrayfunnel' ) : __( 'Inactive', 'arrayfunnel' ) }

{ bump.product_name ? `${ bump.product_name } • ${ toPlainPrice( bump.product_price_formatted ) }` : sprintf( /* translators: %d: product ID. */ __( 'Product #%d', 'arrayfunnel' ), bump.product_id ) }

{ __( 'Template:', 'arrayfunnel' ) }{ ' ' } { bump.template } { __( 'Condition:', 'arrayfunnel' ) }{ ' ' } { bump.condition_type }
) ) }
) } { !! deleting && (

{ __( 'Delete this Order Bump?', 'arrayfunnel' ) }

{ __( 'This action cannot be undone.', 'arrayfunnel' ) }

) }
); } function BumpEditor( { bump, onBack } ) { const qc = useQueryClient(); const isNew = ! bump.id; const [ form, setForm ] = useState( { title: bump.title || '', product_id: bump.product_id || '', condition_type: bump.condition_type || 'all', template: bump.template || 'classic', status: bump.status !== undefined ? bump.status : 1, display_settings: bump.display_settings || { headline: __( 'Special offer', 'arrayfunnel' ), description: __( 'Add this item to your order.', 'arrayfunnel' ), cta_text: __( 'Add to my order', 'arrayfunnel' ), badge_text: __( 'Optional extra', 'arrayfunnel' ), }, } ); const [ productSearch, setProductSearch ] = useState( '' ); const [ productResults, setProductResults ] = useState( [] ); const [ selectedProduct, setSelectedProduct ] = useState( bump.product_id ? { id: bump.product_id, name: bump.product_name, price_value: bump.product_price_value, price_formatted: bump.product_price_formatted, image: bump.product_image, } : null ); const [ saved, setSaved ] = useState( false ); const [ error, setError ] = useState( '' ); const { mutate: save, isPending: saving } = useMutation( { mutationFn: () => isNew ? api.createBump( form ) : api.updateBump( bump.id, form ), onSuccess() { qc.invalidateQueries( [ 'bumps' ] ); setSaved( true ); setTimeout( () => { setSaved( false ); onBack(); }, 1200 ); }, onError( err ) { setError( err.message ); }, } ); const setField = ( k, v ) => setForm( ( f ) => ( { ...f, [ k ]: v, } ) ); const setDisplay = ( k, v ) => setForm( ( f ) => ( { ...f, display_settings: { ...f.display_settings, [ k ]: v, }, } ) ); async function searchProducts( q ) { if ( q.length < 2 ) { setProductResults( [] ); return; } try { const results = await api.searchProducts( q ); setProductResults( results || [] ); } catch ( err ) { setProductResults( [] ); setError( err?.message || __( 'Failed to search products.', 'arrayfunnel' ) ); } } function selectProduct( p ) { setSelectedProduct( p ); setField( 'product_id', p.id ); setProductSearch( p.name ); setProductResults( [] ); } const ctaText = form.display_settings.cta_text || __( 'Add to my order', 'arrayfunnel' ); const previewTemplate = form.template || 'classic'; const previewStyles = { classic: { wrapper: 'rounded-[1.5rem] border border-slate-200 bg-white shadow-sm', badge: 'bg-amber-100 text-amber-700', productCard: 'mt-5 flex items-center gap-4 rounded-[1.5rem] border border-slate-200 bg-slate-50 p-4', productImage: 'h-16 w-16 rounded-3xl object-cover', productInfo: 'min-w-0 flex-1', cta: 'bg-emerald-600 hover:bg-emerald-700 shadow-sm shadow-emerald-500/20', }, highlight: { wrapper: 'relative overflow-hidden rounded-[1.5rem] border border-indigo-200 bg-indigo-50 shadow-sm', inner: 'relative p-5 pt-6', accentBar: 'absolute inset-x-0 top-0 h-2 bg-indigo-500', badge: 'bg-indigo-100 text-indigo-700', productCard: 'mt-5 flex items-center gap-4 rounded-[1.5rem] border border-indigo-200 bg-white p-4 shadow-sm', productImage: 'h-16 w-16 rounded-3xl object-cover', productInfo: 'min-w-0 flex-1', cta: 'bg-indigo-600 hover:bg-indigo-700 shadow-sm shadow-indigo-500/10', }, minimal: { wrapper: 'rounded-[1.5rem] border border-slate-200 bg-white shadow-none', inner: 'p-5', badge: 'text-slate-500', productCard: 'mt-5 flex items-center gap-3 rounded-3xl border border-slate-200 bg-white p-3', productImage: 'h-14 w-14 rounded-2xl object-cover', productInfo: 'min-w-0 flex-1', cta: 'border border-slate-900 bg-white text-slate-900 hover:bg-slate-100 shadow-none', }, }; const previewClasses = previewStyles[ previewTemplate ] || previewStyles.classic; return ( { __( 'Back', 'arrayfunnel' ) } } >
{ !! error && { error } } { !! saved && ( { __( 'Saved successfully.', 'arrayfunnel' ) } ) }
setField( 'title', v ) } placeholder={ __( 'Checkout Offer - Protein Bar', 'arrayfunnel' ) } />
{ setProductSearch( e.target.value ); searchProducts( e.target.value ); } } placeholder={ __( 'Search products…', 'arrayfunnel' ) } className="arrayfunnel-search-control" /> { productResults.length > 0 && (
{ productResults.map( ( p ) => ( ) ) }
) }
{ !! selectedProduct && (
{ !! selectedProduct.image && ( ) } { selectedProduct.name }
) }
setField( 'condition_type', v ) } options={ CONDITIONS } /> setField( 'status', v ? 1 : 0 ) } label={ __( 'Bump active', 'arrayfunnel' ) } description={ __( 'Only active bumps are shown on checkout.', 'arrayfunnel' ) } />
setDisplay( 'headline', v ) } /> setDisplay( 'description', v ) } /> setDisplay( 'cta_text', v ) } /> setDisplay( 'badge_text', v ) } />
{ previewClasses.accentBar && (
) }
{ form.display_settings.badge_text || __( 'Optional extra', 'arrayfunnel' ) }
{ __( 'Template:', 'arrayfunnel' ) }{ ' ' } { previewTemplate .charAt( 0 ) .toUpperCase() + previewTemplate.slice( 1 ) }

{ form.display_settings.headline || __( 'Recommended add-on', 'arrayfunnel' ) }

{ form.display_settings.description || __( 'Add this item to your order.', 'arrayfunnel' ) }

{ selectedProduct?.image && ( { ) }

{ selectedProduct?.name || __( 'Selected product will appear here', 'arrayfunnel' ) }

{ selectedProduct?.price_formatted ? (

{ toPlainPrice( selectedProduct.price_formatted ) }

) : (

{ __( 'Choose a product to preview price', 'arrayfunnel' ) }

) }
{ [ __( 'Headline', 'arrayfunnel' ), __( 'Description', 'arrayfunnel' ), __( 'Product Summary', 'arrayfunnel' ), __( 'CTA Button', 'arrayfunnel' ), ].map( ( item, index ) => (
{ index + 1 } { item }
) ) }
); } function toPlainPrice( value ) { if ( ! value ) { return ''; } const withoutHtml = String( value ).replace( /<[^>]*>/g, ' ' ); return decodeHtmlEntities( withoutHtml ).replace( /\s+/g, ' ' ).trim(); }