Files
OpenFlare/frontend/components/auth/require-auth.tsx
T
ryan 5a8722ff07 feat(frontend): 控制台 next-intl 中英双语
接入无 URL 前缀的 zh-CN/en,顶栏与外观设置可切换语言;选择写入 cookie 后刷新生效。
2026-08-16 16:49:16 +08:00

69 lines
1.6 KiB
TypeScript

'use client';
import type { ReactNode } from 'react';
import { Loader2 } from 'lucide-react';
import { ErrorPage } from '@/components/layout/error';
import { useUser } from '@/contexts/user-context';
import { useTranslations } from 'next-intl';
type RequireAuthProps = {
children: ReactNode;
fallback?: ReactNode;
minHeightClassName?: string;
};
/**
* Guards page content until the session user is available.
* Redirect to login is handled by the parent layout; this only covers the content slot.
*/
export function RequireAuth({
children,
fallback,
minHeightClassName = 'min-h-[400px]',
}: RequireAuthProps) {
const { user, loading } = useUser();
if (loading) {
return (
fallback ?? (
<div
className={`flex items-center justify-center ${minHeightClassName}`}
>
<Loader2 className='size-6 animate-spin text-primary' />
</div>
)
);
}
if (!user) {
return null;
}
return <>{children}</>;
}
type RequireAdminAuthProps = {
children: ReactNode;
};
/** Guards admin routes after the shared shell has rendered. */
export function RequireAdminAuth({ children }: RequireAdminAuthProps) {
const { user, loading } = useUser();
const t = useTranslations('auth.requireAuth');
if (loading) {
return (
<div className='flex min-h-[400px] items-center justify-center'>
<Loader2 className='size-6 animate-spin text-primary' />
</div>
);
}
if (!user?.is_admin) {
return <ErrorPage title={t('accessDenied')} message={t('noPermission')} />;
}
return <>{children}</>;
}