feat(tutorial): implement comprehensive tutorial system with step guidance
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:
2026-05-26 16:54:45 -04:00
parent ec4912aa4a
commit d147bd6f27
23 changed files with 1338 additions and 5 deletions
+92
View File
@@ -0,0 +1,92 @@
<template>
<button
v-if="visible"
type="button"
class="help-btn"
aria-label="Show help for this screen"
@click="onClick"
title="Show help"
>
?
</button>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRoute } from 'vue-router'
import {
activeStep,
maybeShow,
tutorialEnabled,
tutorialProgress,
} from './controller'
// Map route names to the tutorial step that should re-fire when the user taps `?`.
// Keep this list lean — only routes that have a tutorial step actually wired.
const routeToStep: Record<string, string> = {
ParentChildrenListView: 'create-child',
ChoreView: 'create-chore',
RewardView: 'create-reward',
NotificationView: 'notification-click',
ParentView: 'select-child',
CreateChore: 'create-chore-image',
EditChore: 'create-chore-image',
CreateKindness: 'create-kindness',
CreatePenalty: 'create-penalty',
CreateRoutine: 'create-routine',
EditRoutine: 'create-routine-add-task',
}
const route = useRoute()
const targetStepId = computed<string | null>(() => {
const name = typeof route.name === 'string' ? route.name : String(route.name ?? '')
return routeToStep[name] ?? null
})
const visible = computed(() => {
if (!tutorialEnabled.value) return false
return targetStepId.value !== null
})
function onClick() {
const id = targetStepId.value
if (!id) return
// Clear any active step so the manual re-fire wins.
activeStep.value = null
// Temporarily clear local "seen" so the controller re-shows it. The server
// state is left alone; on dismiss `markStepSeen` simply no-ops at the server.
if (tutorialProgress.value[id]) {
const progress = { ...tutorialProgress.value }
delete progress[id]
tutorialProgress.value = progress
}
maybeShow(id)
}
</script>
<style scoped>
.help-btn {
width: 36px;
height: 36px;
border-radius: 50%;
border: 0;
background: var(--btn-secondary, rgba(255, 255, 255, 0.85));
color: var(--btn-primary, #667eea);
font-weight: 700;
font-size: 1.05rem;
line-height: 1;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
}
.help-btn:hover {
background: var(--btn-secondary-hover, #e2e8f0);
}
.help-btn:focus-visible {
outline: 2px solid var(--primary, #667eea);
outline-offset: 2px;
}
</style>