Files
zclaw_openfang/desktop/src/lib/use-onboarding.ts
iven ecd7f2e928 fix(desktop): console.log 清理 — 替换为结构化 logger
将 desktop/src 中 23 处 console.log 替换为 createLogger() 结构化日志:
- 生产构建自动静默 debug/info 级别
- 保留 console.error 用于关键错误可见性
- 新增 dompurify 依赖修复 XSS 防护引入缺失

涉及文件: App.tsx, offlineStore.ts, autonomy-manager.ts,
gateway-auth.ts, llm-service.ts, request-helper.ts,
security-index.ts, skill-discovery.ts, use-onboarding.ts 等 16 个文件
2026-03-30 16:22:16 +08:00

132 lines
3.6 KiB
TypeScript

/**
* useOnboarding - Hook for detecting and managing first-time user onboarding
*
* Determines if user needs to go through the onboarding wizard.
* Stores completion status in localStorage.
*/
import { useState, useEffect, useCallback } from 'react';
import { createLogger } from './logger';
const log = createLogger('useOnboarding');
const ONBOARDING_COMPLETED_KEY = 'zclaw-onboarding-completed';
const USER_PROFILE_KEY = 'zclaw-user-profile';
export interface UserProfile {
userName: string;
userRole?: string;
completedAt: string;
}
export interface OnboardingState {
isNeeded: boolean;
isLoading: boolean;
userProfile: UserProfile | null;
markCompleted: (profile: Omit<UserProfile, 'completedAt'>) => void;
resetOnboarding: () => void;
}
/**
* Hook to manage first-time user onboarding
*
* Usage:
* ```tsx
* const { isNeeded, isLoading, markCompleted } = useOnboarding();
*
* if (isNeeded) {
* return <OnboardingWizard onComplete={markCompleted} />;
* }
* ```
*/
export function useOnboarding(): OnboardingState {
const [isNeeded, setIsNeeded] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [userProfile, setUserProfile] = useState<UserProfile | null>(null);
// Check onboarding status on mount
useEffect(() => {
try {
const completed = localStorage.getItem(ONBOARDING_COMPLETED_KEY);
const profileStr = localStorage.getItem(USER_PROFILE_KEY);
if (completed === 'true' && profileStr) {
const profile = JSON.parse(profileStr) as UserProfile;
setUserProfile(profile);
setIsNeeded(false);
} else {
// No onboarding record - first time user
setIsNeeded(true);
}
} catch (err) {
log.warn('Failed to check onboarding status:', err);
setIsNeeded(true);
} finally {
setIsLoading(false);
}
}, []);
// Mark onboarding as completed
const markCompleted = useCallback((profile: Omit<UserProfile, 'completedAt'>) => {
const fullProfile: UserProfile = {
...profile,
completedAt: new Date().toISOString(),
};
try {
localStorage.setItem(ONBOARDING_COMPLETED_KEY, 'true');
localStorage.setItem(USER_PROFILE_KEY, JSON.stringify(fullProfile));
setUserProfile(fullProfile);
setIsNeeded(false);
log.debug('Onboarding completed for user:', profile.userName);
} catch (err) {
console.error('[useOnboarding] Failed to save onboarding status:', err);
}
}, []);
// Reset onboarding (for testing or user request)
const resetOnboarding = useCallback(() => {
try {
localStorage.removeItem(ONBOARDING_COMPLETED_KEY);
localStorage.removeItem(USER_PROFILE_KEY);
setUserProfile(null);
setIsNeeded(true);
log.debug('Onboarding reset');
} catch (err) {
console.error('[useOnboarding] Failed to reset onboarding:', err);
}
}, []);
return {
isNeeded,
isLoading,
userProfile,
markCompleted,
resetOnboarding,
};
}
/**
* Get stored user profile without hook (for use outside React components)
*/
export function getStoredUserProfile(): UserProfile | null {
try {
const profileStr = localStorage.getItem(USER_PROFILE_KEY);
if (profileStr) {
return JSON.parse(profileStr) as UserProfile;
}
} catch (err) {
log.warn('Failed to get user profile:', err);
}
return null;
}
/**
* Check if onboarding is completed (for use outside React components)
*/
export function isOnboardingCompleted(): boolean {
return localStorage.getItem(ONBOARDING_COMPLETED_KEY) === 'true';
}
export default useOnboarding;