Implement activation requirement for protected routes
CI / Backend (build + test) (push) Successful in 1m18s
CI / Frontend (lint + typecheck + build) (push) Successful in 32s

- 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.
This commit is contained in:
Leonid Pershin
2026-07-13 18:36:14 +03:00
parent 39a8b30b03
commit 7f9a441050
4 changed files with 28 additions and 10 deletions
+14
View File
@@ -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 }
}
+4
View File
@@ -44,12 +44,16 @@ function RootLayout() {
<Link to="/dashboard" className="text-muted-foreground hover:text-foreground">
{t('nav.dashboard')}
</Link>
{user.isActivated && (
<>
<Link to="/instructions" className="text-muted-foreground hover:text-foreground">
{t('nav.instructions')}
</Link>
<Link to="/news" className="text-muted-foreground hover:text-foreground">
{t('nav.news')}
</Link>
</>
)}
<Link to="/settings" className="text-muted-foreground hover:text-foreground">
{t('nav.settings')}
</Link>
+2 -2
View File
@@ -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
+2 -2
View File
@@ -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