feat: add onboarding tutorial for new users
Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 3m4s
Some checks failed
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:
@@ -58,7 +58,12 @@
|
||||
|
||||
<!-- Selected day exception list -->
|
||||
<div v-if="selectedDays.size > 0" class="exception-list">
|
||||
<div v-for="idx in sortedSelectedDays" :key="idx" class="exception-row">
|
||||
<div
|
||||
v-for="(idx, i) in sortedSelectedDays"
|
||||
:key="idx"
|
||||
class="exception-row"
|
||||
:data-tutorial="i === 0 ? 'schedule-days-exception' : undefined"
|
||||
>
|
||||
<span class="exception-day-name">{{ DAY_LABELS[idx] }}</span>
|
||||
<div class="exception-right">
|
||||
<template v-if="exceptions.has(idx)">
|
||||
@@ -114,7 +119,7 @@
|
||||
</div>
|
||||
<span class="field-label">{{ intervalDays === 1 ? 'day' : 'days' }}</span>
|
||||
</div>
|
||||
<div class="interval-row">
|
||||
<div class="interval-row" data-tutorial="schedule-interval-start">
|
||||
<label class="field-label">Starting on</label>
|
||||
<DateInputField
|
||||
:modelValue="anchorDate"
|
||||
@@ -161,11 +166,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted, nextTick, watch } from 'vue'
|
||||
import ModalDialog from './ModalDialog.vue'
|
||||
import TimePickerPopover from './TimePickerPopover.vue'
|
||||
import DateInputField from './DateInputField.vue'
|
||||
import { maybeShow as tutorialMaybeShow } from '@/tutorial/controller'
|
||||
import { maybeShow as tutorialMaybeShow, modalTutorialStepId } from '@/tutorial/controller'
|
||||
import {
|
||||
setChoreSchedule,
|
||||
deleteChoreSchedule,
|
||||
@@ -259,11 +264,33 @@ const intervalTime = ref<TimeValue>({
|
||||
const saving = ref(false)
|
||||
const errorMsg = ref<string | null>(null)
|
||||
|
||||
function triggerScheduleTutorial(isDays: boolean) {
|
||||
modalTutorialStepId.value = isDays ? 'schedule-days-chips' : 'schedule-interval-frequency'
|
||||
nextTick(() => {
|
||||
if (isDays) {
|
||||
tutorialMaybeShow(
|
||||
'schedule-days-chips',
|
||||
() => document.querySelector('.day-chips') as HTMLElement | null,
|
||||
)
|
||||
} else {
|
||||
tutorialMaybeShow(
|
||||
'schedule-interval-frequency',
|
||||
() => document.querySelector('.stepper') as HTMLElement | null,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
tutorialMaybeShow(
|
||||
'create-chore-schedule',
|
||||
() => document.querySelector('.day-chips .chip') as HTMLElement | null,
|
||||
)
|
||||
triggerScheduleTutorial(mode.value === 'days')
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
modalTutorialStepId.value = null
|
||||
})
|
||||
|
||||
watch(mode, (newMode) => {
|
||||
triggerScheduleTutorial(newMode === 'days')
|
||||
})
|
||||
|
||||
// ── original snapshot (for dirty detection) ──────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user