Files
digital-pilates/hooks/useAuthGuard.ts
richarjiang 50525f82a1 feat(medications): 优化药品管理功能和登录流程
- 更新默认药品图片为专用图标
- 移除未使用的 loading 状态选择器
- 优化 Apple 登录按钮样式,支持毛玻璃效果和加载状态
- 添加登录成功后返回功能(shouldBack 参数)
- 药品详情页添加信息卡片点击交互
- 添加药品添加页面的登录状态检查
- 增强时间选择器错误处理和数据验证
- 修复药品图片显示逻辑,支持网络图片
- 优化药品卡片样式和布局
- 添加图片加载错误处理
2025-11-11 10:02:37 +08:00

155 lines
4.5 KiB
TypeScript

import AsyncStorage from '@/utils/kvStore';
import { usePathname, useRouter } from 'expo-router';
import { useCallback } from 'react';
import { Alert } from 'react-native';
import { ROUTES } from '@/constants/Routes';
import { useAppDispatch, useAppSelector } from '@/hooks/redux';
import { STORAGE_KEYS, api } from '@/services/api';
import { logout as logoutAction } from '@/store/userSlice';
type RedirectParams = Record<string, string | number | boolean | undefined>;
type EnsureOptions = {
redirectTo?: string;
redirectParams?: RedirectParams;
shouldBack?: boolean; // 登录成功后是否返回上一页而不是重定向
};
export function useAuthGuard() {
const router = useRouter();
const dispatch = useAppDispatch();
const currentPath = usePathname();
const user = useAppSelector(state => state.user);
const isLoggedIn = !!user?.profile?.id;
const ensureLoggedIn = useCallback(async (options?: EnsureOptions): Promise<boolean> => {
if (isLoggedIn) return true;
const redirectTo = options?.redirectTo ?? currentPath ?? ROUTES.TAB_STATISTICS;
const paramsJson = options?.redirectParams ? JSON.stringify(options.redirectParams) : undefined;
const shouldBack = options?.shouldBack;
router.push({
pathname: '/auth/login',
params: {
redirectTo,
...(paramsJson ? { redirectParams: paramsJson } : {}),
...(shouldBack ? { shouldBack: 'true' } : {}),
},
} as any);
return false;
}, [isLoggedIn, router, currentPath]);
const pushIfAuthedElseLogin = useCallback((pathname: string, params?: RedirectParams) => {
if (isLoggedIn) {
router.push({ pathname, params } as any);
return;
}
const paramsJson = params ? JSON.stringify(params) : undefined;
router.push({ pathname: '/auth/login', params: { redirectTo: pathname, ...(paramsJson ? { redirectParams: paramsJson } : {}) } } as any);
}, [isLoggedIn, router]);
const guardHandler = useCallback(
<T extends any[]>(fn: (...args: T) => any | Promise<any>, options?: EnsureOptions) => {
return async (...args: T) => {
const ok = await ensureLoggedIn(options);
if (!ok) return;
return fn(...args);
};
},
[ensureLoggedIn]
);
// 退出登录功能
const handleLogout = useCallback(async () => {
try {
// 调用 Redux action 清除本地状态和缓存
await dispatch(logoutAction()).unwrap();
// 跳转到登录页面
router.push('/auth/login');
} catch (error) {
console.error('退出登录失败:', error);
Alert.alert('错误', '退出登录失败,请稍后重试');
}
}, [dispatch, router]);
// 带确认对话框的退出登录
const confirmLogout = useCallback(() => {
Alert.alert(
'确认退出',
'确定要退出当前账号吗?',
[
{
text: '取消',
style: 'cancel',
},
{
text: '确定',
style: 'default',
onPress: handleLogout,
},
]
);
}, [handleLogout]);
// 注销账号功能
const handleDeleteAccount = useCallback(async () => {
try {
// 调用注销账号API
await api.delete('/api/users/delete-account');
// 清除额外的本地数据
await AsyncStorage.multiRemove(['@user_personal_info', STORAGE_KEYS.onboardingCompleted]);
// 执行退出登录逻辑
await dispatch(logoutAction()).unwrap();
Alert.alert('账号已注销', '您的账号已成功注销', [
{
text: '确定',
onPress: () => router.push('/auth/login'),
},
]);
} catch (error: any) {
console.error('注销账号失败:', error);
const message = error?.message || '注销失败,请稍后重试';
Alert.alert('注销失败', message);
}
}, [dispatch, router]);
// 带确认对话框的注销账号
const confirmDeleteAccount = useCallback(() => {
Alert.alert(
'确认注销账号',
'此操作不可恢复,将删除您的账号及相关数据。确定继续吗?',
[
{
text: '取消',
style: 'cancel',
},
{
text: '确认注销',
style: 'destructive',
onPress: handleDeleteAccount,
},
],
{ cancelable: true }
);
}, [handleDeleteAccount]);
return {
isLoggedIn,
ensureLoggedIn,
pushIfAuthedElseLogin,
guardHandler,
handleLogout,
confirmLogout,
handleDeleteAccount,
confirmDeleteAccount,
} as const;
}