From 7f9a44105066c7e5ca8979171ffab8f493443070 Mon Sep 17 00:00:00 2001 From: Leonid Pershin Date: Mon, 13 Jul 2026 18:36:14 +0300 Subject: [PATCH] Implement activation requirement for protected routes - Added a new `useRequireActivated` hook to enforce user activation before accessing certain routes, redirecting unauthenticated users to the login page and inactive users to the dashboard. - Updated the `InstructionsPage` and `NewsPage` components to utilize the new activation check, enhancing user flow and security. - Conditional rendering of navigation links in the `RootLayout` based on user activation status, improving user experience by hiding inaccessible features. --- frontend/src/features/auth/guards.ts | 14 ++++++++++++++ frontend/src/routes/__root.tsx | 16 ++++++++++------ frontend/src/routes/instructions.tsx | 4 ++-- frontend/src/routes/news.tsx | 4 ++-- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/frontend/src/features/auth/guards.ts b/frontend/src/features/auth/guards.ts index 5b67d7d..2e3d842 100644 --- a/frontend/src/features/auth/guards.ts +++ b/frontend/src/features/auth/guards.ts @@ -37,3 +37,17 @@ export function useRequireAdmin() { return { user, isReady: !isBootstrapping && !!user && user.role === 'admin' } } + +/** Как useRequireAuth, но дополнительно требует активацию — иначе редирект на дашборд (там форма запроса активации). */ +export function useRequireActivated() { + const { user, isBootstrapping } = useAuthStore() + const navigate = useNavigate() + + useEffect(() => { + if (isBootstrapping) return + if (!user) void navigate({ to: '/login' }) + else if (!user.isActivated) void navigate({ to: '/dashboard' }) + }, [isBootstrapping, user, navigate]) + + return { user, isReady: !isBootstrapping && !!user && user.isActivated } +} diff --git a/frontend/src/routes/__root.tsx b/frontend/src/routes/__root.tsx index fb13845..e1416be 100644 --- a/frontend/src/routes/__root.tsx +++ b/frontend/src/routes/__root.tsx @@ -44,12 +44,16 @@ function RootLayout() { {t('nav.dashboard')} - - {t('nav.instructions')} - - - {t('nav.news')} - + {user.isActivated && ( + <> + + {t('nav.instructions')} + + + {t('nav.news')} + + + )} {t('nav.settings')} diff --git a/frontend/src/routes/instructions.tsx b/frontend/src/routes/instructions.tsx index e0a8e84..240b611 100644 --- a/frontend/src/routes/instructions.tsx +++ b/frontend/src/routes/instructions.tsx @@ -1,13 +1,13 @@ import { createFileRoute } from '@tanstack/react-router' import { useTranslation } from 'react-i18next' -import { useRequireAuth } from '@/features/auth/guards' +import { useRequireActivated } from '@/features/auth/guards' import { AppsCatalog } from '@/features/apps/AppsCatalog' export const Route = createFileRoute('/instructions')({ component: InstructionsPage }) function InstructionsPage() { const { t } = useTranslation() - const { isReady } = useRequireAuth() + const { isReady } = useRequireActivated() if (!isReady) return null diff --git a/frontend/src/routes/news.tsx b/frontend/src/routes/news.tsx index aff7c15..c509570 100644 --- a/frontend/src/routes/news.tsx +++ b/frontend/src/routes/news.tsx @@ -1,13 +1,13 @@ import { createFileRoute } from '@tanstack/react-router' import { useTranslation } from 'react-i18next' -import { useRequireAuth } from '@/features/auth/guards' +import { useRequireActivated } from '@/features/auth/guards' import { NewsFeed } from '@/features/news/NewsFeed' export const Route = createFileRoute('/news')({ component: NewsPage }) function NewsPage() { const { t } = useTranslation() - const { isReady } = useRequireAuth() + const { isReady } = useRequireActivated() if (!isReady) return null