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:
@@ -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>
|
||||
Reference in New Issue
Block a user