feat: add onboarding tutorial for new users
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 3m4s
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 3m4s
- Introduced a modular tutorial layer to guide new parents through the app setup process. - Implemented a 3-step forced intro after first sign-in (PIN setup → child creation → chore creation). - Added just-in-time contextual hints for various features as users encounter them. - Persisted user progress on the backend with new fields in the User model. - Created a new tutorial controller and step registry in the frontend for managing tutorial states. - Added Help button for easy access to tutorial tips and a restart option in the user profile. - Ensured accessibility and mobile responsiveness for the tutorial overlay. - Included tests for backend and frontend functionalities related to the tutorial.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
<button
|
||||
v-if="visible"
|
||||
type="button"
|
||||
class="help-btn"
|
||||
class="help-fab"
|
||||
aria-label="Show help for this screen"
|
||||
@click="onClick"
|
||||
title="Show help"
|
||||
@@ -14,32 +14,43 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import {
|
||||
activeStep,
|
||||
maybeShow,
|
||||
tutorialEnabled,
|
||||
tutorialProgress,
|
||||
} from './controller'
|
||||
import { activeStep, maybeShow, tutorialEnabled, tutorialProgress, clearChainProgress, modalTutorialStepId } 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',
|
||||
ParentChildrenListView: 'parent-children-list',
|
||||
ChoreView: 'list-chore-help',
|
||||
KindnessView: 'list-kindness-help',
|
||||
PenaltyView: 'list-penalty-help',
|
||||
RewardView: 'list-reward-help',
|
||||
RoutineView: 'list-routine-help',
|
||||
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',
|
||||
CreateChore: 'edit-chore-name',
|
||||
EditChore: 'edit-chore-name',
|
||||
CreateKindness: 'edit-kindness-name',
|
||||
EditKindness: 'edit-kindness-name',
|
||||
CreatePenalty: 'edit-penalty-name',
|
||||
EditPenalty: 'edit-penalty-name',
|
||||
CreateRoutine: 'edit-routine-name',
|
||||
EditRoutine: 'edit-routine-name',
|
||||
CreateReward: 'edit-reward-name',
|
||||
EditReward: 'edit-reward-name',
|
||||
CreateChild: 'edit-child-name',
|
||||
ChildEditView: 'edit-child-name',
|
||||
ChoreAssignView: 'assign-chore-list',
|
||||
KindnessAssignView: 'assign-kindness-list',
|
||||
PenaltyAssignView: 'assign-penalty-list',
|
||||
RewardAssignView: 'assign-reward-list',
|
||||
RoutineAssignView: 'assign-routine-list',
|
||||
}
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const targetStepId = computed<string | null>(() => {
|
||||
// Modals (e.g. ScheduleModal) can override the route-based step.
|
||||
if (modalTutorialStepId.value) return modalTutorialStepId.value
|
||||
const name = typeof route.name === 'string' ? route.name : String(route.name ?? '')
|
||||
return routeToStep[name] ?? null
|
||||
})
|
||||
@@ -54,39 +65,63 @@ function onClick() {
|
||||
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
|
||||
}
|
||||
// Temporarily clear local "seen" for the whole chain so every step replays.
|
||||
// The server state is left alone; on dismiss `markStepSeen` simply no-ops.
|
||||
clearChainProgress(id)
|
||||
maybeShow(id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.help-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
.help-fab {
|
||||
position: fixed;
|
||||
bottom: 2rem;
|
||||
left: 2rem;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
border: 0;
|
||||
background: var(--btn-secondary, rgba(255, 255, 255, 0.85));
|
||||
background: var(--btn-secondary, rgba(255, 255, 255, 0.92));
|
||||
color: var(--btn-primary, #667eea);
|
||||
font-weight: 700;
|
||||
font-size: 1.05rem;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
z-index: 1300;
|
||||
transition:
|
||||
background 0.18s,
|
||||
box-shadow 0.18s,
|
||||
transform 0.18s;
|
||||
}
|
||||
.help-btn:hover {
|
||||
.help-fab:hover {
|
||||
background: var(--btn-secondary-hover, #e2e8f0);
|
||||
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.25);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
.help-btn:focus-visible {
|
||||
.help-fab:focus-visible {
|
||||
outline: 2px solid var(--primary, #667eea);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.help-fab {
|
||||
bottom: 1rem;
|
||||
left: 1rem;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
font-size: 1.15rem;
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
backdrop-filter: blur(4px);
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
.help-fab:hover {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
box-shadow: 0 3px 8px rgba(0, 0, 0, 0.2);
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user