feat: 添加训练计划和打卡功能

- 新增训练计划页面,允许用户制定个性化的训练计划
- 集成打卡功能,用户可以记录每日的训练情况
- 更新 Redux 状态管理,添加训练计划相关的 reducer
- 在首页中添加训练计划卡片,支持用户点击跳转
- 更新样式和布局,以适应新功能的展示和交互
- 添加日期选择器和相关依赖,支持用户选择训练日期
This commit is contained in:
richarjiang
2025-08-13 09:10:00 +08:00
parent e0e000b64f
commit f3e6250505
24 changed files with 1898 additions and 609 deletions

63
hooks/useAuthGuard.ts Normal file
View File

@@ -0,0 +1,63 @@
import { usePathname, useRouter } from 'expo-router';
import { useCallback } from 'react';
import { useAppSelector } from '@/hooks/redux';
type RedirectParams = Record<string, string | number | boolean | undefined>;
type EnsureOptions = {
redirectTo?: string;
redirectParams?: RedirectParams;
};
export function useAuthGuard() {
const router = useRouter();
const currentPath = usePathname();
const token = useAppSelector((s) => (s as any)?.user?.token as string | null);
const isLoggedIn = !!token;
const ensureLoggedIn = useCallback(async (options?: EnsureOptions): Promise<boolean> => {
if (isLoggedIn) return true;
const redirectTo = options?.redirectTo ?? currentPath ?? '/(tabs)';
const paramsJson = options?.redirectParams ? JSON.stringify(options.redirectParams) : undefined;
router.push({
pathname: '/auth/login',
params: {
redirectTo,
...(paramsJson ? { redirectParams: paramsJson } : {}),
},
} 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]
);
return {
isLoggedIn,
ensureLoggedIn,
pushIfAuthedElseLogin,
guardHandler,
} as const;
}