improve small details on tutorials (#7320)

* Allow new operator so see the tutorial banner

* Remove banner if user has skipped all tutorials

* Remove tutorials banner if all tutorials marked as completed

* Remove banner if all tutorials marked as completed, by role

* Use derived instead of function for Svelte 5 good practices

* Add error handling

* Add comments

* Create shared Constants for TUTORIAL_DISMISSED_KEY
This commit is contained in:
Tristan TR
2025-12-09 12:20:19 +01:00
committed by GitHub
parent d2bf7e3bb7
commit f6274b6c8e
3 changed files with 80 additions and 10 deletions
@@ -5,26 +5,86 @@
import { goto } from '$app/navigation'
import { sendUserToast, type ToastAction } from '$lib/toast'
import { getLocalSetting, storeLocalSetting } from '$lib/utils'
import { skipAllTodos, syncTutorialsTodos } from '$lib/tutorialUtils'
import {
skipAllTodos,
syncTutorialsTodos,
TUTORIAL_BANNER_DISMISSED_KEY
} from '$lib/tutorialUtils'
import { tutorialsToDo, userStore } from '$lib/stores'
import { TUTORIALS_CONFIG } from '$lib/tutorials/config'
import { hasRoleAccess } from '$lib/tutorials/roleUtils'
import { onMount } from 'svelte'
const DISMISSED_KEY = 'tutorial_banner_dismissed'
let isDismissed = $state(false)
onMount(() => {
// Check if banner has been dismissed
isDismissed = getLocalSetting(DISMISSED_KEY) === 'true'
/**
* Get all tutorial indexes that are accessible to the current user based on their role.
* Automatically recomputes when $userStore changes.
*/
const accessibleTutorialIndexes = $derived.by(() => {
const indexes = new Set<number>()
const user = $userStore
for (const tab of Object.values(TUTORIALS_CONFIG)) {
// Check if user has access to this tab category
if (!hasRoleAccess(user, tab.roles)) {
continue
}
for (const tutorial of tab.tutorials) {
// Check if tutorial has an index and user has access to it
if (tutorial.index !== undefined && hasRoleAccess(user, tutorial.roles)) {
indexes.add(tutorial.index)
}
}
}
return indexes
})
onMount(async () => {
try {
// Sync tutorial progress from backend first
await syncTutorialsTodos()
// Check if banner has been manually dismissed
const manuallyDismissed = getLocalSetting(TUTORIAL_BANNER_DISMISSED_KEY) === 'true'
// Safe to check tutorialsToDo here since we awaited syncTutorialsTodos() above
// Filter tutorialsToDo to only include tutorials accessible to the user
const remainingAccessibleTutorials = $tutorialsToDo.filter((index) =>
accessibleTutorialIndexes.has(index)
)
// Check if all accessible tutorials are completed
const allTutorialsCompleted = remainingAccessibleTutorials.length === 0
// Dismiss banner if manually dismissed OR all accessible tutorials completed
if (manuallyDismissed || allTutorialsCompleted) {
isDismissed = true
// Set localStorage when all tutorials are completed to persist dismissal
// Note: This will re-set the key on every page load if localStorage is cleared
// but tutorials remain completed in backend. This is intentional - the banner
// should stay hidden if tutorials are completed, regardless of localStorage state.
if (allTutorialsCompleted) {
storeLocalSetting(TUTORIAL_BANNER_DISMISSED_KEY, 'true')
}
}
} catch (error) {
console.error('Failed to sync tutorial progress:', error)
// Fallback to manual dismissal check only if API call fails
isDismissed = getLocalSetting(TUTORIAL_BANNER_DISMISSED_KEY) === 'true'
}
})
async function handleSkipAllTutorials() {
await skipAllTodos()
await syncTutorialsTodos()
storeLocalSetting(DISMISSED_KEY, 'true')
storeLocalSetting(TUTORIAL_BANNER_DISMISSED_KEY, 'true')
isDismissed = true
}
function dismissBanner() {
storeLocalSetting(DISMISSED_KEY, 'true')
storeLocalSetting(TUTORIAL_BANNER_DISMISSED_KEY, 'true')
isDismissed = true
const actions: ToastAction[] = [
+12
View File
@@ -3,6 +3,12 @@ import { tutorialsToDo, skippedAll } from './stores'
import { UserService } from './gen'
import { TUTORIALS_CONFIG } from './tutorials/config'
/**
* LocalStorage key for tracking if the tutorial banner has been dismissed.
* Shared between tutorialUtils and TutorialBanner component.
*/
export const TUTORIAL_BANNER_DISMISSED_KEY = 'tutorial_banner_dismissed'
/**
* Get the maximum tutorial index from the config.
* This ensures we don't hardcode the max ID and it automatically updates when tutorials are added.
@@ -64,6 +70,12 @@ export async function skipAllTodos() {
}
tutorialsToDo.set([])
skippedAll.set(true)
// Dismiss the tutorial banner when all tutorials are skipped
if (typeof window !== 'undefined') {
localStorage.setItem(TUTORIAL_BANNER_DISMISSED_KEY, 'true')
}
await UserService.updateTutorialProgress({ requestBody: { progress: bits, skipped_all: true } })
}
@@ -274,9 +274,7 @@
{/if}
</PageHeader>
{#if !$userStore?.operator}
<TutorialBanner />
{/if}
<TutorialBanner />
{#if !$userStore?.operator}
<div class="w-full overflow-auto scrollbar-hidden pb-2">