feat: add onboarding tutorial for new users
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:
2026-06-19 17:27:35 -04:00
parent d147bd6f27
commit e2bb9cd6b9
24 changed files with 1764 additions and 627 deletions
@@ -18,6 +18,7 @@
import { ref, onMounted, computed, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import EntityEditForm from '../shared/EntityEditForm.vue'
import { maybeShow as tutorialMaybeShow } from '@/tutorial/controller'
import '@/assets/styles.css'
const props = defineProps<{ id?: string }>()
@@ -46,6 +47,7 @@ const loading = ref(false)
const error = ref<string | null>(null)
onMounted(async () => {
if (!isEdit.value) tutorialMaybeShow('edit-reward-name')
if (isEdit.value && props.id) {
loading.value = true
try {
+1 -15
View File
@@ -36,7 +36,7 @@
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue'
import { ref, onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
import ItemList from '../shared/ItemList.vue'
import MessageBlock from '@/components/shared/MessageBlock.vue'
@@ -47,11 +47,6 @@ import type { Reward } from '@/common/models'
import { REWARD_FIELDS } from '@/common/models'
import { eventBus } from '@/common/eventBus'
import {
maybeShow as tutorialMaybeShow,
markStepSeen as tutorialMark,
tutorialReady,
} from '@/tutorial/controller'
const $router = useRouter()
@@ -67,15 +62,6 @@ function handleRewardModified(event: any) {
}
}
watch([rewardCountRef, tutorialReady], ([count, ready]) => {
if (!ready) return
if (count === 0) {
tutorialMaybeShow('create-reward', () => document.querySelector('.fab') as HTMLElement | null)
} else if (typeof count === 'number' && count > 0) {
void tutorialMark('has-created-reward')
}
})
onMounted(() => {
eventBus.on('reward_modified', handleRewardModified)
})