;
}
return (
);
};
import apiFetch from '@wordpress/api-fetch';
import { safeParseJson } from '@shared/lib/parsing';
import { create } from 'zustand';
import { devtools, persist, createJSONStorage } from 'zustand/middleware';
const key = 'extendify-help-center-tour-progress';
const startingState = {
currentTour: null,
currentStep: undefined,
preparingStep: undefined,
progress: [],
// initialize the state with default values
...(safeParseJson(window.extHelpCenterData.userData.tourData)?.state ?? {}),
};
const state = (set, get) => ({
...startingState,
startTour: async (tourData) => {
const { trackTourProgress, updateProgress, getStepData, onTourPage } =
get();
if (onTourPage(tourData?.settings?.startFrom)) {
await tourData?.onStart?.(tourData);
tourData.steps =
tourData.steps?.filter(
// Filter out steps that define a condition
(s) => s?.showOnlyIf?.() || s?.showOnlyIf?.() === undefined,
) || [];
await getStepData(0, tourData)?.events?.beforeAttach?.(tourData);
}
set({ currentTour: tourData, currentStep: 0, preparingStep: undefined });
// Increment the opened count
const tour = trackTourProgress(tourData.id);
updateProgress(tour.id, {
openedCount: Number(tour.openedCount) + 1,
lastAction: 'started',
});
},
onTourPage: (startFrom = null) => {
const url = window.location.href;
if (startFrom?.includes(url)) return true;
const { currentTour } = get();
return currentTour?.settings?.startFrom?.includes(url);
},
completeCurrentTour: async () => {
const { currentTour, wasCompleted, findTourProgress, updateProgress } =
get();
const tour = findTourProgress(currentTour?.id);
if (!tour?.id) return;
// if already completed, don't update the completedAt
if (!wasCompleted(tour.id)) {
updateProgress(tour.id, {
completedAt: new Date().toISOString(),
lastAction: 'completed',
});
}
// Track how many times it was completed
updateProgress(tour.id, {
completedCount: Number(tour.completedCount) + 1,
lastAction: 'completed',
});
await currentTour?.onDetach?.();
await currentTour?.onFinish?.();
set({ currentTour: null, currentStep: undefined });
// fire an event to update the site assistant tour status in assistant code base.
if (tour?.id === 'site-assistant-tour') {
window.dispatchEvent(
new CustomEvent('extendify-assist:is-tour-finished', {
detail: { isFinished: true },
}),
);
}
},
closeCurrentTour: async (lastAction) => {
const { currentTour, findTourProgress, updateProgress } = get();
const tour = findTourProgress(currentTour?.id);
if (!tour?.id) return;
const additional = {};
if (['redirected'].includes(lastAction)) {
return updateProgress(tour?.id, { lastAction });
}
if (['closed-by-caught-error'].includes(lastAction)) {
return updateProgress(tour?.id, { lastAction, errored: true });
}
if (lastAction === 'closed-manually') {
additional.closedManuallyCount = Number(tour.closedManuallyCount) + 1;
}
await currentTour?.onDetach?.();
await currentTour?.onFinish?.();
updateProgress(tour?.id, { lastAction, ...additional });
set({
currentTour: null,
currentStep: undefined,
preparingStep: undefined,
});
},
findTourProgress: (tourId) =>
get().progress.find((tour) => tour.id === tourId),
wasCompleted: (tourId) => get().findTourProgress(tourId)?.completedAt,
wasOpened: (tourId) =>
Number(get().findTourProgress(tourId)?.openedCount ?? 0) > 0,
isSeen: (tourId) => get().findTourProgress(tourId)?.firstSeenAt,
trackTourProgress: (tourId) => {
const { findTourProgress } = get();
// If we are already tracking it, return that
if (findTourProgress(tourId)) {
return findTourProgress(tourId);
}
set((state) => ({
progress: [
...state.progress,
{
id: tourId,
firstSeenAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
completedAt: null,
lastAction: 'init',
currentStep: 0,
openedCount: 0,
closedManuallyCount: 0,
completedCount: 0,
errored: false,
},
],
}));
return findTourProgress(tourId);
},
updateProgress: (tourId, update) => {
const lastAction = update?.lastAction ?? 'unknown';
set((state) => {
const progress = state.progress.map((tour) => {
if (tour.id === tourId) {
return {
...tour,
...update,
lastAction,
updatedAt: new Date().toISOString(),
};
}
return tour;
});
return { progress };
});
},
getStepData: (step, tour = get().currentTour) => tour?.steps?.[step] ?? {},
hasNextStep: () => {
if (!get().currentTour) return false;
return Number(get().currentStep) < get().currentTour.steps.length - 1;
},
nextStep: async () => {
const { currentTour, goToStep, updateProgress, currentStep } = get();
const step = Number(currentStep) + 1;
await goToStep(step);
updateProgress(currentTour.id, {
currentStep: step,
lastAction: 'next',
});
},
hasPreviousStep: () => {
if (!get().currentTour) return false;
return Number(get().currentStep) > 0;
},
prevStep: async () => {
const { currentTour, goToStep, updateProgress, currentStep } = get();
const step = currentStep - 1;
await goToStep(step);
updateProgress(currentTour.id, {
currentStep: step,
lastAction: 'prev',
});
},
goToStep: async (step) => {
const { currentTour, updateProgress, closeCurrentTour, getStepData } =
get();
const tour = currentTour;
// Check that the step is valid
if (step < 0 || step > tour.steps.length - 1) {
closeCurrentTour('closed-by-caught-error');
return;
}
updateProgress(tour.id, {
currentStep: step,
lastAction: `go-to-step-${step}`,
});
const events = getStepData(step)?.events;
if (events?.beforeAttach) {
set(() => ({ preparingStep: step }));
// Make sure the preparing animation runs at least 300ms
await Promise.allSettled([
events.beforeAttach?.(tour),
new Promise((resolve) => setTimeout(resolve, 300)),
]);
set(() => ({ preparingStep: undefined }));
}
set(() => ({ currentStep: step }));
},
});
const path = '/extendify/v1/help-center/tour-data';
const storage = {
getItem: async () => await apiFetch({ path }),
setItem: async (_name, state) =>
await apiFetch({ path, method: 'POST', data: { state } }),
};
export const useTourStore = create(
persist(devtools(state, { name: 'Extendify Tour Progress' }), {
name: key,
storage: createJSONStorage(() => storage),
skipHydration: true,
partialize: (state) => {
// return without currentTour or currentStep
// eslint-disable-next-line no-unused-vars
const { currentTour, currentStep, preparingStep, ...newState } = state;
return newState;
},
}),
);
import BarChart from './BarChart';
import Checkmark from './Checkmark';
import Design from './Design';
import Donate from './Donate';
import LeftCaret from './LeftCaret';
import Logo from './Logo';
import Monetization from './Monetization';
import OpenEnvelope from './OpenEnvelope';
import Pencil from './Pencil';
import Planner from './Planner';
import PreviewIcon from './PreviewIcon';
import PriceTag from './PriceTag';
import Radio from './Radio';
import RefreshIcon from './RefreshIcon';
import RightCaret from './RightCaret';
import School from './School';
import SearchIcon from './SearchIcon';
import Shop from './Shop';
import Speech from './Speech';
import Spinner from './Spinner';
import SpinnerIcon from './SpinnerIcon';
import Ticket from './Ticket';
export {
BarChart,
Checkmark,
Design,
Donate,
LeftCaret,
Logo,
Monetization,
OpenEnvelope,
Pencil,
Planner,
PreviewIcon,
PriceTag,
Radio,
School,
RefreshIcon,
RightCaret,
SearchIcon,
Shop,
Speech,
Spinner,
SpinnerIcon,
Ticket,
};
{"version":"1.0","provider_name":"Nossa M\u00eddia","provider_url":"https:\/\/nossamidia.net.br","author_name":"pauloc","author_url":"https:\/\/nossamidia.net.br\/author\/pauloc\/","title":"Daisy Howarth","type":"rich","width":600,"height":338,"html":"
Daisy Howarth<\/a><\/blockquote>