Skip to content

Commit 1538e54

Browse files
committed
fix "More tasks" step
1 parent 7671bcc commit 1538e54

File tree

5 files changed

+209
-57
lines changed

5 files changed

+209
-57
lines changed

assets/src/components/OnboardingWizard/OnboardTask.js

Lines changed: 114 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,30 @@ import { useTaskCompletion } from '../../hooks/useTaskCompletion';
1414
/**
1515
* OnboardTask component.
1616
*
17-
* @param {Object} props - Component props.
18-
* @param {Object} props.task - Task data.
19-
* @param {Object} props.config - Wizard configuration.
20-
* @param {Function} props.onComplete - Callback when task is completed.
17+
* @param {Object} props - Component props.
18+
* @param {Object} props.task - Task data.
19+
* @param {Object} props.config - Wizard configuration.
20+
* @param {Function} props.onComplete - Callback when task is completed.
21+
* @param {Function} props.onOpenChange - Callback when task open state changes.
22+
* @param {boolean} props.forceOpen - If true, render in open state.
23+
* @param {boolean} props.disableActionButton - If true, disable the template's action button by default.
2124
* @return {JSX.Element} OnboardTask component.
2225
*/
23-
export default function OnboardTask( { task, config, onComplete } ) {
26+
export default function OnboardTask( {
27+
task,
28+
config,
29+
onComplete,
30+
onOpenChange,
31+
forceOpen = false,
32+
disableActionButton = false,
33+
} ) {
2434
const { ajaxUrl, nonce } = config;
25-
const { completeTask, isCompleting } = useTaskCompletion( {
35+
const { completeTask } = useTaskCompletion( {
2636
ajaxUrl,
2737
nonce,
2838
} );
2939

30-
const [ isOpen, setIsOpen ] = useState( false );
40+
const [ isOpen, setIsOpen ] = useState( forceOpen );
3141
const [ isCompleted, setIsCompleted ] = useState( false );
3242
const [ formValues, setFormValues ] = useState( {} );
3343
const taskContentRef = useRef( null );
@@ -86,6 +96,9 @@ export default function OnboardTask( { task, config, onComplete } ) {
8696
try {
8797
await completeTask( task.task_id, formValues );
8898
setIsCompleted( true );
99+
// Close the task view and return to task list.
100+
setIsOpen( false );
101+
onOpenChange?.( false );
89102
onComplete?.( task.task_id );
90103
} catch ( error ) {
91104
console.error( 'Failed to complete task:', error );
@@ -97,42 +110,20 @@ export default function OnboardTask( { task, config, onComplete } ) {
97110
*/
98111
const handleOpen = () => {
99112
setIsOpen( true );
113+
onOpenChange?.( true );
100114
};
101115

102116
/**
103117
* Handle close task.
104118
*/
105119
const handleClose = () => {
106120
setIsOpen( false );
121+
onOpenChange?.( false );
107122
};
108123

109124
if ( isOpen ) {
110125
return (
111126
<div className="prpl-task-content-active" ref={ taskContentRef }>
112-
<div className="prpl-task-buttons">
113-
<button
114-
type="button"
115-
className="prpl-btn prpl-task-close-btn"
116-
onClick={ handleClose }
117-
>
118-
<span className="dashicons dashicons-arrow-left-alt2"></span>
119-
{ config?.l10n?.backToRecommendations ||
120-
__(
121-
'Back to recommendations',
122-
'progress-planner'
123-
) }
124-
</button>
125-
<button
126-
type="button"
127-
className="prpl-complete-task-btn"
128-
onClick={ handleComplete }
129-
disabled={ isCompleting }
130-
>
131-
{ isCompleting
132-
? __( 'Completing…', 'progress-planner' )
133-
: __( 'Complete', 'progress-planner' ) }
134-
</button>
135-
</div>
136127
<div className="prpl-task-form">
137128
{ isLoadingTemplate && (
138129
<div className="prpl-spinner">
@@ -196,16 +187,99 @@ export default function OnboardTask( { task, config, onComplete } ) {
196187
tabIndex={ -1 }
197188
ref={ ( el ) => {
198189
if ( el && templateHtml ) {
199-
// Re-initialize file upload handlers after template is rendered.
200-
const fileInputs =
201-
el.querySelectorAll(
202-
'input[type="file"]'
190+
// Prevent duplicate button creation on re-renders.
191+
if (
192+
el.querySelector( '.prpl-task-buttons' )
193+
) {
194+
return;
195+
}
196+
197+
const actionBtn = el.querySelector(
198+
'.prpl-complete-task-btn'
199+
);
200+
201+
if ( actionBtn ) {
202+
// Create button wrapper like develop branch does.
203+
const buttonWrapper =
204+
document.createElement( 'div' );
205+
buttonWrapper.className =
206+
'prpl-task-buttons';
207+
208+
// Create close button.
209+
const closeBtn =
210+
document.createElement( 'button' );
211+
closeBtn.type = 'button';
212+
closeBtn.className =
213+
'prpl-btn prpl-task-close-btn';
214+
closeBtn.innerHTML =
215+
'<span class="dashicons dashicons-arrow-left-alt2"></span> ' +
216+
( config?.l10n
217+
?.backToRecommendations ||
218+
'Back to recommendations' );
219+
closeBtn.addEventListener(
220+
'click',
221+
handleClose
203222
);
204-
// File upload handling will be done by existing JavaScript if available.
205-
// eslint-disable-next-line no-unused-vars
206-
fileInputs.forEach( () => {
207-
// File inputs are handled by existing event listeners.
208-
} );
223+
224+
// Insert wrapper before action button, then move buttons into it.
225+
actionBtn.parentNode.insertBefore(
226+
buttonWrapper,
227+
actionBtn
228+
);
229+
buttonWrapper.appendChild( closeBtn );
230+
buttonWrapper.appendChild( actionBtn );
231+
232+
// Disable action button by default if requested.
233+
if ( disableActionButton ) {
234+
actionBtn.disabled = true;
235+
actionBtn.classList.add(
236+
'prpl-btn-disabled'
237+
);
238+
239+
// Enable button when user makes a selection.
240+
const enableButton = () => {
241+
actionBtn.disabled = false;
242+
actionBtn.classList.remove(
243+
'prpl-btn-disabled'
244+
);
245+
};
246+
247+
// Watch for form input changes.
248+
const inputs = el.querySelectorAll(
249+
'input, select, textarea'
250+
);
251+
inputs.forEach( ( input ) => {
252+
input.addEventListener(
253+
'change',
254+
enableButton
255+
);
256+
input.addEventListener(
257+
'input',
258+
enableButton
259+
);
260+
} );
261+
262+
// Watch for file uploads.
263+
const fileInputs =
264+
el.querySelectorAll(
265+
'input[type="file"]'
266+
);
267+
fileInputs.forEach(
268+
( fileInput ) => {
269+
fileInput.addEventListener(
270+
'change',
271+
enableButton
272+
);
273+
}
274+
);
275+
276+
// Watch for custom events (e.g., from media uploader).
277+
el.addEventListener(
278+
'prpl-task-input-changed',
279+
enableButton
280+
);
281+
}
282+
}
209283
}
210284
} }
211285
/>

assets/src/components/OnboardingWizard/steps/MoreTasksStep.js

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export default function MoreTasksStep( props ) {
2525

2626
const [ currentSubStep, setCurrentSubStep ] = useState( 0 );
2727
const [ completedTasks, setCompletedTasks ] = useState( {} );
28+
const [ openTaskId, setOpenTaskId ] = useState( null );
2829

2930
const tasks = stepData?.data?.tasks || [];
3031

@@ -164,21 +165,90 @@ export default function MoreTasksStep( props ) {
164165
}
165166

166167
// Tasks sub-step.
168+
// If a task is open, only show that task's expanded view.
169+
if ( openTaskId ) {
170+
const openTask = tasks.find( ( t ) => t.task_id === openTaskId );
171+
if ( openTask ) {
172+
return (
173+
<div
174+
className="prpl-more-tasks-substep"
175+
data-substep="more-tasks-tasks"
176+
>
177+
<OnboardTask
178+
key={ openTask.task_id }
179+
task={ openTask }
180+
config={ config }
181+
onComplete={ handleTaskComplete }
182+
onOpenChange={ ( isOpen ) =>
183+
setOpenTaskId(
184+
isOpen ? openTask.task_id : null
185+
)
186+
}
187+
forceOpen
188+
disableActionButton
189+
/>
190+
</div>
191+
);
192+
}
193+
}
194+
195+
// Show task list when no task is open.
167196
return (
168197
<div
169198
className="prpl-more-tasks-substep prpl-columns-wrapper-flex"
170199
data-substep="more-tasks-tasks"
171200
>
172201
<div className="prpl-column">
173202
<ul className="prpl-task-list">
174-
{ tasks.map( ( task ) => (
175-
<OnboardTask
176-
key={ task.task_id }
177-
task={ task }
178-
config={ config }
179-
onComplete={ handleTaskComplete }
180-
/>
181-
) ) }
203+
{ tasks.map( ( task ) => {
204+
const isTaskCompleted =
205+
completedTasks[ task.task_id ];
206+
return (
207+
<li
208+
key={ task.task_id }
209+
className="prpl-complete-task-item"
210+
>
211+
<span className="task-title">
212+
<span className="prpl-task-arrow">
213+
&rarr;
214+
</span>
215+
{ task.title }
216+
</span>
217+
<div
218+
className={ `prpl-task-item${
219+
isTaskCompleted
220+
? ' prpl-task-completed'
221+
: ''
222+
}` }
223+
>
224+
<div className="prpl-task-item-button-wrapper">
225+
<button
226+
type="button"
227+
className="prpl-complete-task-btn"
228+
onClick={ () =>
229+
setOpenTaskId(
230+
task.task_id
231+
)
232+
}
233+
disabled={ isTaskCompleted }
234+
>
235+
{ task.action_label ||
236+
__(
237+
'Do it',
238+
'progress-planner'
239+
) }
240+
</button>
241+
<span className="prpl-suggested-task-points">
242+
+1
243+
</span>
244+
</div>
245+
<span className="prpl-task-completed-icon">
246+
{ '\u2713' }
247+
</span>
248+
</div>
249+
</li>
250+
);
251+
} ) }
182252
</ul>
183253
</div>
184254
</div>
@@ -188,8 +258,8 @@ export default function MoreTasksStep( props ) {
188258
return (
189259
<OnboardingStep { ...props } hideFooter>
190260
<div className="tour-content">{ renderSubStep() }</div>
191-
{ /* Show footer only on tasks sub-step */ }
192-
{ currentSubStep === 1 && (
261+
{ /* Show footer only on tasks sub-step when no task is open */ }
262+
{ currentSubStep === 1 && ! openTaskId && (
193263
<NextButton
194264
onNext={ handleFinish }
195265
canProceed={ () => true }

build/dashboard.asset.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<?php return array('dependencies' => array('react', 'react-jsx-runtime', 'wp-api-fetch', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => 'aa78e1a348010dd199e7', 'handle' => 'undefined-dashboard');
1+
<?php return array('dependencies' => array('react', 'react-jsx-runtime', 'wp-api-fetch', 'wp-element', 'wp-hooks', 'wp-i18n'), 'version' => '1b5b16104251732d1ace', 'handle' => 'undefined-dashboard');

0 commit comments

Comments
 (0)