PluginProbe
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More / 2.2.0
Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More v2.2.0
2.4.1 2.4.0 2.3.4 2.3.3 2.3.2 2.3.1 2.3.0 2.2.2 2.2.1 2.2.0 2.1.2 2.1.1 trunk 0.0.1 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 All 68 releases
better-payment / src / campaign-builder / components / SuggestedAmountsEditor.js

SuggestedAmountsEditor.js in Better Payment – Instant Payments, Donations, Fundraising with Subscriptions & More 2.2.0, at src/campaign-builder/components/SuggestedAmountsEditor.js

181 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { __ } from '@wordpress/i18n';
2 import { getGlobalCurrencySymbol } from '../utils/currency';
3 import { DndContext, PointerSensor, closestCenter, useSensor, useSensors } from '@dnd-kit/core';
4 import { SortableContext, verticalListSortingStrategy, useSortable, arrayMove } from '@dnd-kit/sortable';
5 import { CSS } from '@dnd-kit/utilities';
6
7 function DragHandle( { listeners, attributes } ) {
8 return (
9 <span
10 className="bp-sa-drag"
11 { ...listeners }
12 { ...attributes }
13 title={ __( 'Drag to reorder', 'better-payment' ) }
14 >
15 ⠿
16 </span>
17 );
18 }
19
20 const currencySymbol = getGlobalCurrencySymbol();
21
22 function SortableAmountRow( { item, onUpdate, onDelete, onSetDefault, compact } ) {
23 const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable( { id: item.id } );
24
25 const style = {
26 transform: CSS.Transform.toString( transform ),
27 transition,
28 opacity: isDragging ? 0.4 : 1,
29 zIndex: isDragging ? 1 : 'auto',
30 };
31
32 return (
33 <div ref={ setNodeRef } style={ style } className="bp-sa-row">
34 <DragHandle listeners={ listeners } attributes={ attributes } />
35
36 <div className="bp-sa-cell bp-sa-cell--radio">
37 <button
38 type="button"
39 className={ 'bp-sa-radio' + ( item.is_default ? ' is-selected' : '' ) }
40 onClick={ () => onSetDefault( item.id ) }
41 title={ __( 'Set as default', 'better-payment' ) }
42 />
43 </div>
44
45 <div className={ 'bp-sa-cell bp-sa-cell--amount' + ( compact ? ' bp-sa-cell--amount-fill' : '' ) }>
46 { compact ? (
47 <div className="bp-sa-amount-prefix-wrap">
48 <span className="bp-sa-amount-prefix">{ currencySymbol }</span>
49 <input
50 type="number"
51 min="0"
52 step="1"
53 value={ item.amount }
54 onChange={ ( e ) => onUpdate( item.id, { amount: e.target.value } ) }
55 placeholder="0"
56 />
57 </div>
58 ) : (
59 <input
60 type="number"
61 min="0"
62 step="1"
63 value={ item.amount }
64 onChange={ ( e ) => onUpdate( item.id, { amount: e.target.value } ) }
65 placeholder="0"
66 />
67 ) }
68 </div>
69
70 { ! compact && (
71 <div className="bp-sa-cell bp-sa-cell--desc">
72 <input
73 type="text"
74 value={ item.description }
75 onChange={ ( e ) => onUpdate( item.id, { description: e.target.value } ) }
76 placeholder={ __( 'Optional description', 'better-payment' ) }
77 />
78 </div>
79 ) }
80
81 <button
82 type="button"
83 className="bp-sa-delete"
84 onClick={ () => onDelete( item.id ) }
85 title={ __( 'Remove', 'better-payment' ) }
86 >
87 ✕
88 </button>
89 </div>
90 );
91 }
92
93 /**
94 * Drag-and-drop suggested donation amounts editor.
95 *
96 * @param {Object[]} items Array of { id, amount, description, is_default }
97 * @param {Function} onChange Called with the updated array.
98 * @param {boolean} compact When true: hides description column, shows $ prefix on amount.
99 */
100 export default function SuggestedAmountsEditor( { items, onChange, compact = false } ) {
101 const sensors = useSensors(
102 useSensor( PointerSensor, { activationConstraint: { distance: 5 } } )
103 );
104
105 const safeItems = Array.isArray( items ) ? items : [];
106
107 const handleDragEnd = ( { active, over } ) => {
108 if ( over && active.id !== over.id ) {
109 const oldIndex = safeItems.findIndex( ( i ) => i.id === active.id );
110 const newIndex = safeItems.findIndex( ( i ) => i.id === over.id );
111 onChange( arrayMove( safeItems, oldIndex, newIndex ) );
112 }
113 };
114
115 const updateItem = ( id, patch ) =>
116 onChange( safeItems.map( ( i ) => ( i.id === id ? { ...i, ...patch } : i ) ) );
117
118 const deleteItem = ( id ) =>
119 onChange( safeItems.filter( ( i ) => i.id !== id ) );
120
121 const setDefault = ( id ) =>
122 onChange( safeItems.map( ( i ) => ( { ...i, is_default: i.id === id } ) ) );
123
124 const addItem = () =>
125 onChange( [
126 ...safeItems,
127 { id: 'sa_' + Date.now(), amount: '', description: '', is_default: false },
128 ] );
129
130 const clearDefaults = () =>
131 onChange( safeItems.map( ( i ) => ( { ...i, is_default: false } ) ) );
132
133 return (
134 <div className={ 'bp-sa-editor' + ( compact ? ' bp-sa-editor--compact' : '' ) }>
135 { safeItems.length > 0 && (
136 <div className="bp-sa-header">
137 <span className="bp-sa-header__drag" />
138 <span className="bp-sa-header__cell bp-sa-header__cell--radio">
139 { __( 'Default', 'better-payment' ) }
140 </span>
141 <span className={ 'bp-sa-header__cell bp-sa-header__cell--amount' + ( compact ? ' bp-sa-header__cell--amount-fill' : '' ) }>
142 { __( 'Amount', 'better-payment' ) }
143 </span>
144 { ! compact && (
145 <span className="bp-sa-header__cell bp-sa-header__cell--desc">
146 { __( 'Description (optional)', 'better-payment' ) }
147 </span>
148 ) }
149 <span className="bp-sa-header__del" />
150 </div>
151 ) }
152
153 <DndContext sensors={ sensors } collisionDetection={ closestCenter } onDragEnd={ handleDragEnd }>
154 <SortableContext items={ safeItems } strategy={ verticalListSortingStrategy }>
155 { safeItems.map( ( item ) => (
156 <SortableAmountRow
157 key={ item.id }
158 item={ item }
159 onUpdate={ updateItem }
160 onDelete={ deleteItem }
161 onSetDefault={ setDefault }
162 compact={ compact }
163 />
164 ) ) }
165 </SortableContext>
166 </DndContext>
167
168 <div className="bp-sa-footer">
169 <button type="button" className={ 'bp-sa-add' + ( compact ? ' bp-sa-add--full' : '' ) } onClick={ addItem }>
170 + { __( 'Add A Suggested Amount', 'better-payment' ) }
171 </button>
172 { ! compact && safeItems.length > 0 && (
173 <button type="button" className="bp-sa-clear" onClick={ clearDefaults }>
174 { __( 'Clear Defaults', 'better-payment' ) }
175 </button>
176 ) }
177 </div>
178 </div>
179 );
180 }
181