mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-22 16:02:24 +00:00
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:
@@ -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[] = [
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user