feat(tutorial): implement comprehensive tutorial system with step guidance
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 3m9s
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 3m9s
- Added tutorial controller to manage tutorial state and progress. - Introduced HelpButton component for contextual help throughout the application. - Created various tutorial steps for onboarding and feature guidance. - Integrated tutorial prompts in multiple components (ChildrenListView, LoginButton, ScheduleModal, etc.) to enhance user experience. - Implemented logic to show tutorials based on user actions and state. - Added functionality to dismiss and skip tutorial sessions. - Established a mechanism to hydrate tutorial state from user profile.
This commit is contained in:
@@ -27,6 +27,7 @@ import {
|
||||
triggerRoutineAsParent,
|
||||
} from '@/common/api'
|
||||
import { eventBus } from '@/common/eventBus'
|
||||
import { maybeShow as tutorialMaybeShow } from '@/tutorial/controller'
|
||||
import '@/assets/styles.css'
|
||||
import type {
|
||||
Task,
|
||||
@@ -687,6 +688,36 @@ function openChoreMenu(taskId: string, e: MouseEvent) {
|
||||
menuPosition.value = { top: rect.bottom, left: rect.right - 140 }
|
||||
}
|
||||
activeMenuFor.value = taskId
|
||||
tutorialMaybeShow(
|
||||
'chore-kebab',
|
||||
() => document.querySelector('.kebab-menu') as HTMLElement | null,
|
||||
)
|
||||
const items: ChildTask[] = childChoreListRef.value?.items ?? []
|
||||
const task = items.find((t) => t.id === taskId)
|
||||
if (task) {
|
||||
if (isChoreExpired(task)) {
|
||||
nextTick(() => {
|
||||
tutorialMaybeShow(
|
||||
'chore-kebab-extend-time',
|
||||
() =>
|
||||
Array.from(document.querySelectorAll('.kebab-menu .menu-item')).find((el) =>
|
||||
/extend\s*time/i.test(el.textContent || ''),
|
||||
) as HTMLElement | null,
|
||||
)
|
||||
})
|
||||
}
|
||||
if (isChoreCompletedToday(task)) {
|
||||
nextTick(() => {
|
||||
tutorialMaybeShow(
|
||||
'chore-kebab-reset',
|
||||
() =>
|
||||
Array.from(document.querySelectorAll('.kebab-menu .menu-item')).find((el) =>
|
||||
/^reset/i.test((el.textContent || '').trim()),
|
||||
) as HTMLElement | null,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closeChoreMenu() {
|
||||
@@ -1002,6 +1033,13 @@ onMounted(async () => {
|
||||
child.value = data
|
||||
tasks.value = data.tasks || []
|
||||
rewards.value = data.rewards || []
|
||||
// Fire the per-child overview tour (chains into assign-* steps).
|
||||
nextTick(() => {
|
||||
tutorialMaybeShow(
|
||||
'select-child',
|
||||
() => document.querySelector('.assign-buttons') as HTMLElement | null,
|
||||
)
|
||||
})
|
||||
}
|
||||
loading.value = false
|
||||
if (scrollToId) {
|
||||
@@ -1028,6 +1066,36 @@ onMounted(async () => {
|
||||
}
|
||||
})
|
||||
|
||||
// Fire status-badge tutorials when those badges first render for any chore.
|
||||
watch(
|
||||
() => {
|
||||
const items: ChildTask[] = childChoreListRef.value?.items ?? []
|
||||
return items.map((t) => ({ id: t.id, expired: isChoreExpired(t), pending: isChorePending(t) }))
|
||||
},
|
||||
(list) => {
|
||||
if (list.some((t) => t.expired)) {
|
||||
nextTick(() => {
|
||||
tutorialMaybeShow(
|
||||
'status-too-late',
|
||||
() =>
|
||||
Array.from(document.querySelectorAll('.chore-stamp')).find(
|
||||
(el) => (el.textContent || '').trim() === 'TOO LATE',
|
||||
) as HTMLElement | null,
|
||||
)
|
||||
})
|
||||
}
|
||||
if (list.some((t) => t.pending)) {
|
||||
nextTick(() => {
|
||||
tutorialMaybeShow(
|
||||
'status-pending',
|
||||
() => document.querySelector('.chore-stamp.pending-stamp') as HTMLElement | null,
|
||||
)
|
||||
})
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
onUnmounted(() => {
|
||||
eventBus.off('child_task_triggered', handleTaskTriggered)
|
||||
eventBus.off('child_reward_triggered', handleRewardTriggered)
|
||||
|
||||
Reference in New Issue
Block a user