mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-20 04:01:24 +02:00
- Create @manacore/shared-app-onboarding package with: - createAppOnboardingStore factory function (Svelte 5 runes) - MiniOnboardingModal component for select/toggle/info steps - TypeScript types for flexible step configuration - Integrate into Calendar app with questions for: - Week start (Monday/Sunday) - Default view (Day/Week/Month) - Timezone preference (Auto/Manual) - Welcome tips The mini-onboarding stores completion state in deviceSettings, allowing per-device, per-app onboarding experiences.
232 lines
5.1 KiB
TypeScript
232 lines
5.1 KiB
TypeScript
import type {
|
||
AppOnboardingConfig,
|
||
AppOnboardingStore,
|
||
AppOnboardingPreferences,
|
||
AppOnboardingStep,
|
||
} from './types';
|
||
|
||
const ONBOARDING_COMPLETED_KEY = 'onboarding_completed';
|
||
const ONBOARDING_PREFERENCES_KEY = 'onboarding_preferences';
|
||
|
||
/**
|
||
* Create an app-specific onboarding store
|
||
*
|
||
* This factory creates a store that:
|
||
* - Checks if onboarding was already completed (via deviceSettings)
|
||
* - Manages step navigation and preference collection
|
||
* - Saves completion state and preferences to deviceSettings
|
||
*
|
||
* @example
|
||
* ```typescript
|
||
* import { createAppOnboardingStore } from '@manacore/shared-app-onboarding';
|
||
*
|
||
* const calendarOnboarding = createAppOnboardingStore({
|
||
* appId: 'calendar',
|
||
* steps: [
|
||
* {
|
||
* id: 'weekStart',
|
||
* type: 'select',
|
||
* question: 'Wann beginnt deine Woche?',
|
||
* emoji: '📅',
|
||
* options: [
|
||
* { id: 'monday', label: 'Montag', emoji: '1️⃣' },
|
||
* { id: 'sunday', label: 'Sonntag', emoji: '7️⃣' },
|
||
* ],
|
||
* defaultValue: 'monday',
|
||
* },
|
||
* ],
|
||
* userSettings,
|
||
* });
|
||
* ```
|
||
*/
|
||
export function createAppOnboardingStore(config: AppOnboardingConfig): AppOnboardingStore {
|
||
const { appId, steps, userSettings, onComplete, onSkip } = config;
|
||
|
||
// State
|
||
let currentStep = $state(0);
|
||
let preferences = $state<AppOnboardingPreferences>({});
|
||
let saving = $state(false);
|
||
let completed = $state(false);
|
||
|
||
// Initialize preferences with default values
|
||
for (const step of steps) {
|
||
if (step.type === 'select' && step.defaultValue !== undefined) {
|
||
preferences[step.id] = step.defaultValue;
|
||
} else if (step.type === 'toggle' && step.defaultValue !== undefined) {
|
||
preferences[step.id] = step.defaultValue;
|
||
}
|
||
}
|
||
|
||
// Derived values
|
||
const totalSteps = $derived(steps.length);
|
||
const isFirstStep = $derived(currentStep === 0);
|
||
const isLastStep = $derived(currentStep === steps.length - 1);
|
||
const progress = $derived(((currentStep + 1) / steps.length) * 100);
|
||
const currentStepConfig = $derived<AppOnboardingStep | undefined>(steps[currentStep]);
|
||
|
||
/**
|
||
* Check if onboarding was already completed
|
||
*/
|
||
function checkCompleted(): boolean {
|
||
const deviceAppSettings = userSettings.getDeviceAppSettings();
|
||
return deviceAppSettings[ONBOARDING_COMPLETED_KEY] === true;
|
||
}
|
||
|
||
// Derived: should show modal
|
||
const shouldShow = $derived(!completed && !checkCompleted() && userSettings.loaded);
|
||
|
||
/**
|
||
* Go to next step
|
||
*/
|
||
function next(): void {
|
||
if (currentStep < steps.length - 1) {
|
||
currentStep++;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Go to previous step
|
||
*/
|
||
function prev(): void {
|
||
if (currentStep > 0) {
|
||
currentStep--;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Go to specific step
|
||
*/
|
||
function goToStep(index: number): void {
|
||
if (index >= 0 && index < steps.length) {
|
||
currentStep = index;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Set a preference value
|
||
*/
|
||
function setPreference(key: string, value: unknown): void {
|
||
preferences = { ...preferences, [key]: value };
|
||
}
|
||
|
||
/**
|
||
* Complete the onboarding and save preferences
|
||
*/
|
||
async function complete(): Promise<void> {
|
||
saving = true;
|
||
try {
|
||
// Save to deviceSettings
|
||
await userSettings.updateDeviceAppSettings({
|
||
[ONBOARDING_COMPLETED_KEY]: true,
|
||
[ONBOARDING_PREFERENCES_KEY]: preferences,
|
||
...preferences, // Also spread preferences at top level for easy access
|
||
});
|
||
|
||
completed = true;
|
||
|
||
// Call completion callback
|
||
if (onComplete) {
|
||
await onComplete(preferences);
|
||
}
|
||
} finally {
|
||
saving = false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Skip the onboarding entirely
|
||
*/
|
||
async function skip(): Promise<void> {
|
||
saving = true;
|
||
try {
|
||
// Mark as completed but don't save preferences
|
||
await userSettings.updateDeviceAppSettings({
|
||
[ONBOARDING_COMPLETED_KEY]: true,
|
||
onboarding_skipped: true,
|
||
});
|
||
|
||
completed = true;
|
||
|
||
// Call skip callback
|
||
if (onSkip) {
|
||
await onSkip();
|
||
}
|
||
} finally {
|
||
saving = false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Reset onboarding (for testing/debugging)
|
||
*/
|
||
async function reset(): Promise<void> {
|
||
saving = true;
|
||
try {
|
||
// Remove onboarding flags from deviceSettings
|
||
await userSettings.updateDeviceAppSettings({
|
||
[ONBOARDING_COMPLETED_KEY]: false,
|
||
onboarding_skipped: false,
|
||
});
|
||
|
||
completed = false;
|
||
currentStep = 0;
|
||
|
||
// Reset preferences to defaults
|
||
preferences = {};
|
||
for (const step of steps) {
|
||
if (step.type === 'select' && step.defaultValue !== undefined) {
|
||
preferences[step.id] = step.defaultValue;
|
||
} else if (step.type === 'toggle' && step.defaultValue !== undefined) {
|
||
preferences[step.id] = step.defaultValue;
|
||
}
|
||
}
|
||
} finally {
|
||
saving = false;
|
||
}
|
||
}
|
||
|
||
return {
|
||
get shouldShow() {
|
||
return shouldShow;
|
||
},
|
||
get currentStep() {
|
||
return currentStep;
|
||
},
|
||
get totalSteps() {
|
||
return totalSteps;
|
||
},
|
||
get isFirstStep() {
|
||
return isFirstStep;
|
||
},
|
||
get isLastStep() {
|
||
return isLastStep;
|
||
},
|
||
get progress() {
|
||
return progress;
|
||
},
|
||
get currentStepConfig() {
|
||
return currentStepConfig;
|
||
},
|
||
get preferences() {
|
||
return preferences;
|
||
},
|
||
get saving() {
|
||
return saving;
|
||
},
|
||
get appId() {
|
||
return appId;
|
||
},
|
||
get steps() {
|
||
return steps;
|
||
},
|
||
|
||
next,
|
||
prev,
|
||
goToStep,
|
||
setPreference,
|
||
complete,
|
||
skip,
|
||
reset,
|
||
checkCompleted,
|
||
};
|
||
}
|