From 11ec08aac3a9496853af8569d9977f8ab169da88 Mon Sep 17 00:00:00 2001 From: Sluijsens Date: Fri, 31 Jul 2026 22:30:37 +0200 Subject: [PATCH] Restructures frontend to a feature-based folder layout Groups auth, setup, invitation, profile, users, cms, availability and system code (services/hooks, components, schemas, mocks, pages) under src/features/ instead of splitting by technical layer (api/, components/, lib/schemas/, mocks/, pages/). Renames the old connection-oriented `api` layer to `services` per feature, and splits the monolithic api/types.ts into per-feature types.ts files (with ProblemDetails/ApiResult merged into lib/api-client.ts as shared infra). Layout-agnostic code (ui primitives, app shell, i18n, test utils, lib) stays at the top level. Co-Authored-By: Claude Sonnet 5 --- frontend/src/api/types.ts | 127 ------------------ frontend/src/components/layout/AppLayout.tsx | 4 +- .../src/components/layout/Sidebar.test.tsx | 2 +- frontend/src/components/layout/Sidebar.tsx | 6 +- frontend/src/components/layout/UserMenu.tsx | 2 +- frontend/src/components/shared/RoleBadge.tsx | 2 +- .../auth/components}/RoleGuard.test.tsx | 2 +- .../auth/components}/RoleGuard.tsx | 4 +- .../auth/context}/AuthContext.test.tsx | 6 +- .../auth/context}/AuthProvider.tsx | 4 +- .../auth/context}/auth-context.ts | 2 +- .../auth => features/auth/mocks}/fixtures.ts | 2 +- .../auth => features/auth/mocks}/handlers.ts | 3 +- .../auth}/pages/LoginPage.test.tsx | 2 +- .../{ => features/auth}/pages/LoginPage.tsx | 4 +- .../auth}/schemas/auth.test.ts | 2 +- .../{lib => features/auth}/schemas/auth.ts | 0 frontend/src/features/auth/services/types.ts | 21 +++ .../AvailabilityStatusBadge.test.tsx | 0 .../components}/AvailabilityStatusBadge.tsx | 2 +- .../availability/mocks}/handlers.ts | 0 .../features/availability/services/types.ts | 9 ++ .../services}/useAvailability.test.ts | 2 +- .../availability/services}/useAvailability.ts | 0 .../components}/AddCmsInstanceDialog.test.tsx | 4 +- .../cms/components}/AddCmsInstanceDialog.tsx | 4 +- .../cms/components}/CmsInstanceList.tsx | 2 +- .../cms/components}/SetStatusDialog.test.tsx | 2 +- .../cms/components}/SetStatusDialog.tsx | 6 +- .../cms => features/cms/mocks}/handlers.ts | 4 +- .../{ => features/cms}/pages/CmsPage.test.tsx | 4 +- .../src/{ => features/cms}/pages/CmsPage.tsx | 10 +- .../{lib => features/cms}/schemas/cms.test.ts | 0 .../src/{lib => features/cms}/schemas/cms.ts | 0 frontend/src/features/cms/services/types.ts | 28 ++++ .../cms/services}/useAddCmsInstance.test.ts | 2 +- .../cms/services}/useAddCmsInstance.ts | 0 .../cms/services}/useCmsInstances.test.ts | 4 +- .../cms/services}/useCmsInstances.ts | 0 .../useUpdateCmsInstanceStatus.test.ts | 2 +- .../services}/useUpdateCmsInstanceStatus.ts | 0 .../dashboard}/pages/DashboardPage.test.tsx | 0 .../dashboard}/pages/DashboardPage.tsx | 6 +- .../components}/InviteUserDialog.test.tsx | 2 +- .../components}/InviteUserDialog.tsx | 4 +- .../invitation/mocks}/handlers.ts | 2 +- .../pages/InviteCompletePage.test.tsx | 0 .../invitation}/pages/InviteCompletePage.tsx | 4 +- .../src/features/invitation/services/types.ts | 12 ++ .../services}/useInvitation.test.ts | 4 +- .../invitation/services}/useInvitation.ts | 0 .../profile}/pages/ProfilePage.test.tsx | 2 +- .../profile}/pages/ProfilePage.tsx | 4 +- .../profile/services}/useProfile.test.ts | 6 +- .../profile/services}/useProfile.ts | 4 +- .../settings}/pages/SettingsPage.test.tsx | 2 +- .../settings}/pages/SettingsPage.tsx | 6 +- .../setup/mocks}/handlers.ts | 4 +- .../setup}/pages/SetupPage.test.tsx | 0 .../{ => features/setup}/pages/SetupPage.tsx | 4 +- frontend/src/features/setup/services/types.ts | 10 ++ .../setup/services}/useInitGuard.test.ts | 4 +- .../setup/services}/useInitGuard.ts | 2 +- .../setup/services}/useSetup.test.ts | 4 +- .../setup/services}/useSetup.ts | 2 +- .../system/components}/ModuleGuard.tsx | 2 +- .../system/mocks}/handlers.ts | 0 .../src/features/system/services/types.ts | 6 + .../system/services}/useSystemCapabilities.ts | 0 .../users/components}/DeleteUserDialog.tsx | 2 +- .../users/mocks}/handlers.ts | 4 +- .../users}/pages/UsersPage.test.tsx | 4 +- .../{ => features/users}/pages/UsersPage.tsx | 11 +- .../users}/schemas/users.test.ts | 0 .../{lib => features/users}/schemas/users.ts | 0 frontend/src/features/users/services/types.ts | 20 +++ .../users/services}/useUsers.test.ts | 4 +- .../users/services}/useUsers.ts | 3 +- frontend/src/lib/api-client.ts | 15 ++- frontend/src/main.tsx | 4 +- frontend/src/mocks/index.ts | 30 ++--- frontend/src/router.tsx | 24 ++-- frontend/src/test/utils.tsx | 6 +- 83 files changed, 249 insertions(+), 254 deletions(-) delete mode 100644 frontend/src/api/types.ts rename frontend/src/{components/auth => features/auth/components}/RoleGuard.test.tsx (94%) rename frontend/src/{components/auth => features/auth/components}/RoleGuard.tsx (89%) rename frontend/src/{contexts => features/auth/context}/AuthContext.test.tsx (93%) rename frontend/src/{contexts => features/auth/context}/AuthProvider.tsx (96%) rename frontend/src/{contexts => features/auth/context}/auth-context.ts (93%) rename frontend/src/{mocks/auth => features/auth/mocks}/fixtures.ts (93%) rename frontend/src/{mocks/auth => features/auth/mocks}/handlers.ts (93%) rename frontend/src/{ => features/auth}/pages/LoginPage.test.tsx (96%) rename frontend/src/{ => features/auth}/pages/LoginPage.tsx (97%) rename frontend/src/{lib => features/auth}/schemas/auth.test.ts (99%) rename frontend/src/{lib => features/auth}/schemas/auth.ts (100%) create mode 100644 frontend/src/features/auth/services/types.ts rename frontend/src/{components/shared => features/availability/components}/AvailabilityStatusBadge.test.tsx (100%) rename frontend/src/{components/shared => features/availability/components}/AvailabilityStatusBadge.tsx (97%) rename frontend/src/{mocks/availability => features/availability/mocks}/handlers.ts (100%) create mode 100644 frontend/src/features/availability/services/types.ts rename frontend/src/{api => features/availability/services}/useAvailability.test.ts (97%) rename frontend/src/{api => features/availability/services}/useAvailability.ts (100%) rename frontend/src/{components/cms => features/cms/components}/AddCmsInstanceDialog.test.tsx (95%) rename frontend/src/{components/cms => features/cms/components}/AddCmsInstanceDialog.tsx (97%) rename frontend/src/{components/cms => features/cms/components}/CmsInstanceList.tsx (98%) rename frontend/src/{components/cms => features/cms/components}/SetStatusDialog.test.tsx (96%) rename frontend/src/{components/cms => features/cms/components}/SetStatusDialog.tsx (96%) rename frontend/src/{mocks/cms => features/cms/mocks}/handlers.ts (94%) rename frontend/src/{ => features/cms}/pages/CmsPage.test.tsx (95%) rename frontend/src/{ => features/cms}/pages/CmsPage.tsx (87%) rename frontend/src/{lib => features/cms}/schemas/cms.test.ts (100%) rename frontend/src/{lib => features/cms}/schemas/cms.ts (100%) create mode 100644 frontend/src/features/cms/services/types.ts rename frontend/src/{api => features/cms/services}/useAddCmsInstance.test.ts (96%) rename frontend/src/{api => features/cms/services}/useAddCmsInstance.ts (100%) rename frontend/src/{api => features/cms/services}/useCmsInstances.test.ts (93%) rename frontend/src/{api => features/cms/services}/useCmsInstances.ts (100%) rename frontend/src/{api => features/cms/services}/useUpdateCmsInstanceStatus.test.ts (97%) rename frontend/src/{api => features/cms/services}/useUpdateCmsInstanceStatus.ts (100%) rename frontend/src/{ => features/dashboard}/pages/DashboardPage.test.tsx (100%) rename frontend/src/{ => features/dashboard}/pages/DashboardPage.tsx (90%) rename frontend/src/{components/users => features/invitation/components}/InviteUserDialog.test.tsx (98%) rename frontend/src/{components/users => features/invitation/components}/InviteUserDialog.tsx (97%) rename frontend/src/{mocks/invitation => features/invitation/mocks}/handlers.ts (96%) rename frontend/src/{ => features/invitation}/pages/InviteCompletePage.test.tsx (100%) rename frontend/src/{ => features/invitation}/pages/InviteCompletePage.tsx (99%) create mode 100644 frontend/src/features/invitation/services/types.ts rename frontend/src/{api => features/invitation/services}/useInvitation.test.ts (97%) rename frontend/src/{api => features/invitation/services}/useInvitation.ts (100%) rename frontend/src/{ => features/profile}/pages/ProfilePage.test.tsx (98%) rename frontend/src/{ => features/profile}/pages/ProfilePage.tsx (98%) rename frontend/src/{api => features/profile/services}/useProfile.test.ts (93%) rename frontend/src/{api => features/profile/services}/useProfile.ts (86%) rename frontend/src/{ => features/settings}/pages/SettingsPage.test.tsx (98%) rename frontend/src/{ => features/settings}/pages/SettingsPage.tsx (96%) rename frontend/src/{mocks/setup => features/setup/mocks}/handlers.ts (94%) rename frontend/src/{ => features/setup}/pages/SetupPage.test.tsx (100%) rename frontend/src/{ => features/setup}/pages/SetupPage.tsx (98%) create mode 100644 frontend/src/features/setup/services/types.ts rename frontend/src/{auth => features/setup/services}/useInitGuard.test.ts (94%) rename frontend/src/{auth => features/setup/services}/useInitGuard.ts (97%) rename frontend/src/{api => features/setup/services}/useSetup.test.ts (95%) rename frontend/src/{api => features/setup/services}/useSetup.ts (96%) rename frontend/src/{components/auth => features/system/components}/ModuleGuard.tsx (94%) rename frontend/src/{mocks/system => features/system/mocks}/handlers.ts (100%) create mode 100644 frontend/src/features/system/services/types.ts rename frontend/src/{api => features/system/services}/useSystemCapabilities.ts (100%) rename frontend/src/{components/users => features/users/components}/DeleteUserDialog.tsx (96%) rename frontend/src/{mocks/users => features/users/mocks}/handlers.ts (96%) rename frontend/src/{ => features/users}/pages/UsersPage.test.tsx (95%) rename frontend/src/{ => features/users}/pages/UsersPage.tsx (98%) rename frontend/src/{lib => features/users}/schemas/users.test.ts (100%) rename frontend/src/{lib => features/users}/schemas/users.ts (100%) create mode 100644 frontend/src/features/users/services/types.ts rename frontend/src/{api => features/users/services}/useUsers.test.ts (97%) rename frontend/src/{api => features/users/services}/useUsers.ts (95%) diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts deleted file mode 100644 index 6e3292c..0000000 --- a/frontend/src/api/types.ts +++ /dev/null @@ -1,127 +0,0 @@ -// Domain types shared across the frontend. Names align with backend payloads -// (the user property is `name`, never `naam`) — see BR-U1-10. - -// Roles aligned with backend authorization. -export type UserRole = 'Owner' | 'Administrator' | 'User'; - -export interface User { - id: string; // UUID - email: string; - name: string; - role: UserRole; - isActive: boolean; -} - -export interface AuthResponse { - accessToken: string; // JWT access token - expiresAt: string; // ISO timestamp - user: User; -} - -export interface LoginRequest { - email: string; - password: string; -} - -// RFC 9457 ProblemDetails for standardized error handling. -export interface ProblemDetails { - type?: string; - title?: string; - status?: number; - detail?: string; - instance?: string; - // Extension members (e.g. traceId, errors). - [extension: string]: unknown; -} - -// Convenience wrapper for API results (optional usage). -export type ApiResult = { ok: true; data: T } | { ok: false; error: ProblemDetails }; - -// Setup status (used by guards during bootstrap). -export interface SetupStatus { - initialized: boolean; -} - -export interface SetupRequest { - name: string; - email: string; - password: string; -} - -export interface InvitationValidation { - email: string; - name: string | null; - isValid: boolean; - errorCode: 'EXPIRED' | 'USED' | 'NOT_FOUND' | null; -} - -export interface InviteCompleteRequest { - token: string; - name: string; - password: string; -} - -export interface UserListItem extends User { - createdAt: string; - invitationPending: boolean; - inviteLink: string | null; -} - -export interface InviteUserPayload { - email: string; - role: Exclude; -} - -export interface InviteUserResponse { - inviteLink: string; -} - -export interface ChangeRolePayload { - newRole: UserRole; -} - -export type AvailabilityStatus = 'Available' | 'Maintenance' | 'NotAvailable'; - -export type CmsInstanceStatus = 'Available' | 'NotAvailable' | 'Inactive'; - -export interface CmsInstance { - id: string; - name: string; - url: string; - status: CmsInstanceStatus; - disableMessage: string | null; - lastContactedAt: string | null; - lastStatusPushedAt: string | null; - lastIntegrityCheckFailedAt: string | null; -} - -export interface CreateCmsInstanceRequest { - name: string; - url: string; - apiKey: string; -} - -export interface UpdateCmsInstanceStatusRequest { - status: CmsInstanceStatus; - disableMessage: string | null; -} - -export interface UpdateStatusResult { - success: boolean; - slaveContactSuccess: boolean; -} - -export interface AvailabilityResponse { - status: AvailabilityStatus; - checkedAt: string; // ISO 8601 - message: string; - /** True when a Master CMS has taken control of this instance's gate — the local status cannot be changed here. */ - isMasterControlled: boolean; -} - -// Which optional modules (e.g. "Master") this backend instance has loaded — -// lets the frontend tell a master-only feature apart from a slave instance -// without that module (local master/slave dev setup). -export interface SystemCapabilities { - modules: string[]; -} diff --git a/frontend/src/components/layout/AppLayout.tsx b/frontend/src/components/layout/AppLayout.tsx index 63de7fc..a417c6e 100644 --- a/frontend/src/components/layout/AppLayout.tsx +++ b/frontend/src/components/layout/AppLayout.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react'; import { Outlet, useNavigate, useRouterState } from '@tanstack/react-router'; -import { useAuth } from '@/contexts/auth-context'; -import { useAvailabilityStatus } from '@/api/useAvailability'; +import { useAuth } from '@/features/auth/context/auth-context'; +import { useAvailabilityStatus } from '@/features/availability/services/useAvailability'; import { Sidebar } from '@/components/layout/Sidebar'; import { MobileBar } from '@/components/layout/MobileBar'; import { SidebarOverlay } from '@/components/layout/SidebarOverlay'; diff --git a/frontend/src/components/layout/Sidebar.test.tsx b/frontend/src/components/layout/Sidebar.test.tsx index f066428..f1ebd16 100644 --- a/frontend/src/components/layout/Sidebar.test.tsx +++ b/frontend/src/components/layout/Sidebar.test.tsx @@ -5,7 +5,7 @@ import { server } from '@/mocks/server'; import { setupHandlers } from '@/mocks/index'; import { _resetSetupStatusCache } from '@/router'; import { http, HttpResponse } from 'msw'; -import { API_BASE, makeAuthResponse, mockUser } from '@/mocks/auth/fixtures'; +import { API_BASE, makeAuthResponse, mockUser } from '@/features/auth/mocks/fixtures'; beforeEach(() => { _resetSetupStatusCache(); diff --git a/frontend/src/components/layout/Sidebar.tsx b/frontend/src/components/layout/Sidebar.tsx index 7f2a5d0..d842cab 100644 --- a/frontend/src/components/layout/Sidebar.tsx +++ b/frontend/src/components/layout/Sidebar.tsx @@ -3,9 +3,9 @@ import { useTranslation } from 'react-i18next'; import { LayoutDashboard, Users, FileText, Settings, X } from 'lucide-react'; import type { LucideIcon } from 'lucide-react'; import { cn } from '@/lib/utils'; -import { useAuth } from '@/contexts/auth-context'; -import { useAvailabilityStatus } from '@/api/useAvailability'; -import { useSystemCapabilities } from '@/api/useSystemCapabilities'; +import { useAuth } from '@/features/auth/context/auth-context'; +import { useAvailabilityStatus } from '@/features/availability/services/useAvailability'; +import { useSystemCapabilities } from '@/features/system/services/useSystemCapabilities'; import { LanguageSwitcher } from '@/i18n/LanguageSwitcher'; import { ThemeToggle } from './ThemeToggle'; import { UserMenu } from './UserMenu'; diff --git a/frontend/src/components/layout/UserMenu.tsx b/frontend/src/components/layout/UserMenu.tsx index 969bd8b..a41aa77 100644 --- a/frontend/src/components/layout/UserMenu.tsx +++ b/frontend/src/components/layout/UserMenu.tsx @@ -11,7 +11,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import { useAuth } from '@/contexts/auth-context'; +import { useAuth } from '@/features/auth/context/auth-context'; export function UserMenu() { const { t } = useTranslation(); diff --git a/frontend/src/components/shared/RoleBadge.tsx b/frontend/src/components/shared/RoleBadge.tsx index 8ecb6ee..52b8102 100644 --- a/frontend/src/components/shared/RoleBadge.tsx +++ b/frontend/src/components/shared/RoleBadge.tsx @@ -1,6 +1,6 @@ import { useTranslation } from 'react-i18next'; import { Badge } from '@/components/ui/badge'; -import type { UserRole } from '@/api/types'; +import type { UserRole } from '@/features/auth/services/types'; function roleBadgeVariant(role: UserRole): 'default' | 'secondary' | 'outline' { if (role === 'Owner') return 'default'; diff --git a/frontend/src/components/auth/RoleGuard.test.tsx b/frontend/src/features/auth/components/RoleGuard.test.tsx similarity index 94% rename from frontend/src/components/auth/RoleGuard.test.tsx rename to frontend/src/features/auth/components/RoleGuard.test.tsx index 1e2068d..0f38010 100644 --- a/frontend/src/components/auth/RoleGuard.test.tsx +++ b/frontend/src/features/auth/components/RoleGuard.test.tsx @@ -3,7 +3,7 @@ import { screen } from '@testing-library/react'; import { http, HttpResponse } from 'msw'; import { renderApp } from '@/test/utils'; import { server } from '@/mocks/server'; -import { API_BASE, makeAuthResponse, mockUser } from '@/mocks/auth/fixtures'; +import { API_BASE, makeAuthResponse, mockUser } from '@/features/auth/mocks/fixtures'; import { _resetSetupStatusCache } from '@/router'; beforeEach(() => { diff --git a/frontend/src/components/auth/RoleGuard.tsx b/frontend/src/features/auth/components/RoleGuard.tsx similarity index 89% rename from frontend/src/components/auth/RoleGuard.tsx rename to frontend/src/features/auth/components/RoleGuard.tsx index 72b32c7..f60b091 100644 --- a/frontend/src/components/auth/RoleGuard.tsx +++ b/frontend/src/features/auth/components/RoleGuard.tsx @@ -1,8 +1,8 @@ import type { ReactNode } from 'react'; import { Link } from '@tanstack/react-router'; import { useTranslation } from 'react-i18next'; -import type { UserRole } from '@/api/types'; -import { useAuth } from '@/contexts/auth-context'; +import type { UserRole } from '@/features/auth/services/types'; +import { useAuth } from '@/features/auth/context/auth-context'; interface RoleGuardProps { allowedRoles: UserRole[]; diff --git a/frontend/src/contexts/AuthContext.test.tsx b/frontend/src/features/auth/context/AuthContext.test.tsx similarity index 93% rename from frontend/src/contexts/AuthContext.test.tsx rename to frontend/src/features/auth/context/AuthContext.test.tsx index 48997c0..d9c8e05 100644 --- a/frontend/src/contexts/AuthContext.test.tsx +++ b/frontend/src/features/auth/context/AuthContext.test.tsx @@ -3,11 +3,11 @@ import { act, renderHook, waitFor } from '@testing-library/react'; import { http, HttpResponse } from 'msw'; import { I18nextProvider } from 'react-i18next'; import type { ReactNode } from 'react'; -import { AuthProvider } from '@/contexts/AuthProvider'; -import { useAuth } from '@/contexts/auth-context'; +import { AuthProvider } from '@/features/auth/context/AuthProvider'; +import { useAuth } from '@/features/auth/context/auth-context'; import { api } from '@/lib/api-client'; import { server } from '@/mocks/server'; -import { API_BASE, mockUser } from '@/mocks/auth/fixtures'; +import { API_BASE, mockUser } from '@/features/auth/mocks/fixtures'; import { mockGuest } from '@/test/utils'; import i18n from '@/i18n/config'; diff --git a/frontend/src/contexts/AuthProvider.tsx b/frontend/src/features/auth/context/AuthProvider.tsx similarity index 96% rename from frontend/src/contexts/AuthProvider.tsx rename to frontend/src/features/auth/context/AuthProvider.tsx index 5ce50ab..c6efc4b 100644 --- a/frontend/src/contexts/AuthProvider.tsx +++ b/frontend/src/features/auth/context/AuthProvider.tsx @@ -1,7 +1,7 @@ import { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react'; -import type { AuthResponse } from '@/api/types'; +import type { AuthResponse } from '@/features/auth/services/types'; import { api } from '@/lib/api-client'; -import { AuthContext, type AuthContextValue, type AuthStatus } from '@/contexts/auth-context'; +import { AuthContext, type AuthContextValue, type AuthStatus } from '@/features/auth/context/auth-context'; /** * Owns the in-memory authentication session. On mount it attempts a silent diff --git a/frontend/src/contexts/auth-context.ts b/frontend/src/features/auth/context/auth-context.ts similarity index 93% rename from frontend/src/contexts/auth-context.ts rename to frontend/src/features/auth/context/auth-context.ts index 707419f..08b65aa 100644 --- a/frontend/src/contexts/auth-context.ts +++ b/frontend/src/features/auth/context/auth-context.ts @@ -1,5 +1,5 @@ import { createContext, useContext } from 'react'; -import type { User } from '@/api/types'; +import type { User } from '@/features/auth/services/types'; export type AuthStatus = 'loading' | 'authenticated' | 'guest'; diff --git a/frontend/src/mocks/auth/fixtures.ts b/frontend/src/features/auth/mocks/fixtures.ts similarity index 93% rename from frontend/src/mocks/auth/fixtures.ts rename to frontend/src/features/auth/mocks/fixtures.ts index 445c262..621cdbb 100644 --- a/frontend/src/mocks/auth/fixtures.ts +++ b/frontend/src/features/auth/mocks/fixtures.ts @@ -1,4 +1,4 @@ -import type { AuthResponse, User } from '@/api/types'; +import type { AuthResponse, User } from '@/features/auth/services/types'; import { getAppConfig } from '@/lib/config'; /** diff --git a/frontend/src/mocks/auth/handlers.ts b/frontend/src/features/auth/mocks/handlers.ts similarity index 93% rename from frontend/src/mocks/auth/handlers.ts rename to frontend/src/features/auth/mocks/handlers.ts index d4e3790..9835031 100644 --- a/frontend/src/mocks/auth/handlers.ts +++ b/frontend/src/features/auth/mocks/handlers.ts @@ -1,5 +1,6 @@ import { http, HttpResponse } from 'msw'; -import type { LoginRequest, ProblemDetails } from '@/api/types'; +import type { LoginRequest } from '@/features/auth/services/types'; +import type { ProblemDetails } from '@/lib/api-client'; import { API_BASE, makeAuthResponse, TEST_CREDENTIALS } from './fixtures'; function problem(status: number, title: string, detail?: string): ProblemDetails { diff --git a/frontend/src/pages/LoginPage.test.tsx b/frontend/src/features/auth/pages/LoginPage.test.tsx similarity index 96% rename from frontend/src/pages/LoginPage.test.tsx rename to frontend/src/features/auth/pages/LoginPage.test.tsx index 2ea1232..21c00b1 100644 --- a/frontend/src/pages/LoginPage.test.tsx +++ b/frontend/src/features/auth/pages/LoginPage.test.tsx @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'; import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderApp, mockGuest } from '@/test/utils'; -import { TEST_CREDENTIALS } from '@/mocks/auth/fixtures'; +import { TEST_CREDENTIALS } from '@/features/auth/mocks/fixtures'; describe('LoginPage', () => { it('shows validation errors when submitting an empty form', async () => { diff --git a/frontend/src/pages/LoginPage.tsx b/frontend/src/features/auth/pages/LoginPage.tsx similarity index 97% rename from frontend/src/pages/LoginPage.tsx rename to frontend/src/features/auth/pages/LoginPage.tsx index fe3560a..73081e6 100644 --- a/frontend/src/pages/LoginPage.tsx +++ b/frontend/src/features/auth/pages/LoginPage.tsx @@ -7,9 +7,9 @@ import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; -import { useAuth } from '@/contexts/auth-context'; +import { useAuth } from '@/features/auth/context/auth-context'; import { NetworkError, ProblemDetailsError } from '@/lib/api-client'; -import { loginFormSchema, type LoginFormData } from '@/lib/schemas/auth'; +import { loginFormSchema, type LoginFormData } from '@/features/auth/schemas/auth'; export function LoginPage() { const { t } = useTranslation(); diff --git a/frontend/src/lib/schemas/auth.test.ts b/frontend/src/features/auth/schemas/auth.test.ts similarity index 99% rename from frontend/src/lib/schemas/auth.test.ts rename to frontend/src/features/auth/schemas/auth.test.ts index 48988e2..bb1daca 100644 --- a/frontend/src/lib/schemas/auth.test.ts +++ b/frontend/src/features/auth/schemas/auth.test.ts @@ -4,7 +4,7 @@ import { setupFormSchema, loginFormSchema, invitationCompleteFormSchema, -} from '@/lib/schemas/auth'; +} from '@/features/auth/schemas/auth'; describe('passwordSchema', () => { it('accepts a valid password', () => { diff --git a/frontend/src/lib/schemas/auth.ts b/frontend/src/features/auth/schemas/auth.ts similarity index 100% rename from frontend/src/lib/schemas/auth.ts rename to frontend/src/features/auth/schemas/auth.ts diff --git a/frontend/src/features/auth/services/types.ts b/frontend/src/features/auth/services/types.ts new file mode 100644 index 0000000..ec7a8c9 --- /dev/null +++ b/frontend/src/features/auth/services/types.ts @@ -0,0 +1,21 @@ +// Roles aligned with backend authorization. +export type UserRole = 'Owner' | 'Administrator' | 'User'; + +export interface User { + id: string; // UUID + email: string; + name: string; + role: UserRole; + isActive: boolean; +} + +export interface AuthResponse { + accessToken: string; // JWT access token + expiresAt: string; // ISO timestamp + user: User; +} + +export interface LoginRequest { + email: string; + password: string; +} diff --git a/frontend/src/components/shared/AvailabilityStatusBadge.test.tsx b/frontend/src/features/availability/components/AvailabilityStatusBadge.test.tsx similarity index 100% rename from frontend/src/components/shared/AvailabilityStatusBadge.test.tsx rename to frontend/src/features/availability/components/AvailabilityStatusBadge.test.tsx diff --git a/frontend/src/components/shared/AvailabilityStatusBadge.tsx b/frontend/src/features/availability/components/AvailabilityStatusBadge.tsx similarity index 97% rename from frontend/src/components/shared/AvailabilityStatusBadge.tsx rename to frontend/src/features/availability/components/AvailabilityStatusBadge.tsx index a71930b..22907a6 100644 --- a/frontend/src/components/shared/AvailabilityStatusBadge.tsx +++ b/frontend/src/features/availability/components/AvailabilityStatusBadge.tsx @@ -1,6 +1,6 @@ import { AlertTriangle, CheckCircle, Clock, XCircle } from 'lucide-react'; import { useTranslation } from 'react-i18next'; -import type { AvailabilityStatus } from '@/api/types'; +import type { AvailabilityStatus } from '@/features/availability/services/types'; interface AvailabilityStatusBadgeProps { status: AvailabilityStatus; diff --git a/frontend/src/mocks/availability/handlers.ts b/frontend/src/features/availability/mocks/handlers.ts similarity index 100% rename from frontend/src/mocks/availability/handlers.ts rename to frontend/src/features/availability/mocks/handlers.ts diff --git a/frontend/src/features/availability/services/types.ts b/frontend/src/features/availability/services/types.ts new file mode 100644 index 0000000..cb89c8f --- /dev/null +++ b/frontend/src/features/availability/services/types.ts @@ -0,0 +1,9 @@ +export type AvailabilityStatus = 'Available' | 'Maintenance' | 'NotAvailable'; + +export interface AvailabilityResponse { + status: AvailabilityStatus; + checkedAt: string; // ISO 8601 + message: string; + /** True when a Master CMS has taken control of this instance's gate — the local status cannot be changed here. */ + isMasterControlled: boolean; +} diff --git a/frontend/src/api/useAvailability.test.ts b/frontend/src/features/availability/services/useAvailability.test.ts similarity index 97% rename from frontend/src/api/useAvailability.test.ts rename to frontend/src/features/availability/services/useAvailability.test.ts index ed467e5..5bf4f20 100644 --- a/frontend/src/api/useAvailability.test.ts +++ b/frontend/src/features/availability/services/useAvailability.test.ts @@ -4,7 +4,7 @@ import { http, HttpResponse } from 'msw'; import { createElement } from 'react'; import { describe, expect, it } from 'vitest'; import { server } from '@/mocks/server'; -import { API_BASE } from '@/mocks/auth/fixtures'; +import { API_BASE } from '@/features/auth/mocks/fixtures'; import { useAvailabilityStatus } from './useAvailability'; function createWrapper() { diff --git a/frontend/src/api/useAvailability.ts b/frontend/src/features/availability/services/useAvailability.ts similarity index 100% rename from frontend/src/api/useAvailability.ts rename to frontend/src/features/availability/services/useAvailability.ts diff --git a/frontend/src/components/cms/AddCmsInstanceDialog.test.tsx b/frontend/src/features/cms/components/AddCmsInstanceDialog.test.tsx similarity index 95% rename from frontend/src/components/cms/AddCmsInstanceDialog.test.tsx rename to frontend/src/features/cms/components/AddCmsInstanceDialog.test.tsx index 7541120..7125a9a 100644 --- a/frontend/src/components/cms/AddCmsInstanceDialog.test.tsx +++ b/frontend/src/features/cms/components/AddCmsInstanceDialog.test.tsx @@ -4,8 +4,8 @@ import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; import { renderApp, mockAuthenticated } from '@/test/utils'; import { server } from '@/mocks/server'; -import { API_BASE } from '@/mocks/auth/fixtures'; -import { resetMockCmsInstances } from '@/mocks/cms/handlers'; +import { API_BASE } from '@/features/auth/mocks/fixtures'; +import { resetMockCmsInstances } from '@/features/cms/mocks/handlers'; import { _resetSetupStatusCache } from '@/router'; beforeEach(() => { diff --git a/frontend/src/components/cms/AddCmsInstanceDialog.tsx b/frontend/src/features/cms/components/AddCmsInstanceDialog.tsx similarity index 97% rename from frontend/src/components/cms/AddCmsInstanceDialog.tsx rename to frontend/src/features/cms/components/AddCmsInstanceDialog.tsx index 4a19476..75f6ebb 100644 --- a/frontend/src/components/cms/AddCmsInstanceDialog.tsx +++ b/frontend/src/features/cms/components/AddCmsInstanceDialog.tsx @@ -15,9 +15,9 @@ import { } from '@/components/ui/dialog'; import { FormErrorBanner } from '@/components/ui/FormErrorBanner'; import { FieldError } from '@/components/ui/FieldError'; -import { useAddCmsInstance } from '@/api/useAddCmsInstance'; +import { useAddCmsInstance } from '@/features/cms/services/useAddCmsInstance'; import { NetworkError, ProblemDetailsError } from '@/lib/api-client'; -import { addCmsInstanceSchema, type AddCmsInstanceFormData } from '@/lib/schemas/cms'; +import { addCmsInstanceSchema, type AddCmsInstanceFormData } from '@/features/cms/schemas/cms'; interface AddCmsInstanceDialogProps { open: boolean; diff --git a/frontend/src/components/cms/CmsInstanceList.tsx b/frontend/src/features/cms/components/CmsInstanceList.tsx similarity index 98% rename from frontend/src/components/cms/CmsInstanceList.tsx rename to frontend/src/features/cms/components/CmsInstanceList.tsx index d968e5f..35e56a1 100644 --- a/frontend/src/components/cms/CmsInstanceList.tsx +++ b/frontend/src/features/cms/components/CmsInstanceList.tsx @@ -15,7 +15,7 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Button } from '@/components/ui/button'; -import type { CmsInstance, CmsInstanceStatus } from '@/api/types'; +import type { CmsInstance, CmsInstanceStatus } from '@/features/cms/services/types'; const STATUS_BADGE_CONFIG: Record< CmsInstanceStatus, diff --git a/frontend/src/components/cms/SetStatusDialog.test.tsx b/frontend/src/features/cms/components/SetStatusDialog.test.tsx similarity index 96% rename from frontend/src/components/cms/SetStatusDialog.test.tsx rename to frontend/src/features/cms/components/SetStatusDialog.test.tsx index 0d93064..57239e2 100644 --- a/frontend/src/components/cms/SetStatusDialog.test.tsx +++ b/frontend/src/features/cms/components/SetStatusDialog.test.tsx @@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it } from 'vitest'; import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderApp, mockAuthenticated } from '@/test/utils'; -import { resetMockCmsInstances } from '@/mocks/cms/handlers'; +import { resetMockCmsInstances } from '@/features/cms/mocks/handlers'; import { _resetSetupStatusCache } from '@/router'; beforeEach(() => { diff --git a/frontend/src/components/cms/SetStatusDialog.tsx b/frontend/src/features/cms/components/SetStatusDialog.tsx similarity index 96% rename from frontend/src/components/cms/SetStatusDialog.tsx rename to frontend/src/features/cms/components/SetStatusDialog.tsx index f071075..f4d8a8d 100644 --- a/frontend/src/components/cms/SetStatusDialog.tsx +++ b/frontend/src/features/cms/components/SetStatusDialog.tsx @@ -21,10 +21,10 @@ import { } from '@/components/ui/select'; import { FormErrorBanner } from '@/components/ui/FormErrorBanner'; import { FieldError } from '@/components/ui/FieldError'; -import { useUpdateCmsInstanceStatus } from '@/api/useUpdateCmsInstanceStatus'; +import { useUpdateCmsInstanceStatus } from '@/features/cms/services/useUpdateCmsInstanceStatus'; import { NetworkError } from '@/lib/api-client'; -import { setStatusSchema, type SetStatusFormData } from '@/lib/schemas/cms'; -import type { CmsInstance, CmsInstanceStatus } from '@/api/types'; +import { setStatusSchema, type SetStatusFormData } from '@/features/cms/schemas/cms'; +import type { CmsInstance, CmsInstanceStatus } from '@/features/cms/services/types'; interface SetStatusDialogProps { instance: CmsInstance | null; diff --git a/frontend/src/mocks/cms/handlers.ts b/frontend/src/features/cms/mocks/handlers.ts similarity index 94% rename from frontend/src/mocks/cms/handlers.ts rename to frontend/src/features/cms/mocks/handlers.ts index d837348..183ac06 100644 --- a/frontend/src/mocks/cms/handlers.ts +++ b/frontend/src/features/cms/mocks/handlers.ts @@ -1,6 +1,6 @@ import { http, HttpResponse } from 'msw'; -import type { CmsInstance, UpdateStatusResult } from '@/api/types'; -import { API_BASE } from '../auth/fixtures'; +import type { CmsInstance, UpdateStatusResult } from '@/features/cms/services/types'; +import { API_BASE } from '@/features/auth/mocks/fixtures'; const seed: CmsInstance[] = [ { diff --git a/frontend/src/pages/CmsPage.test.tsx b/frontend/src/features/cms/pages/CmsPage.test.tsx similarity index 95% rename from frontend/src/pages/CmsPage.test.tsx rename to frontend/src/features/cms/pages/CmsPage.test.tsx index a36bca9..a130450 100644 --- a/frontend/src/pages/CmsPage.test.tsx +++ b/frontend/src/features/cms/pages/CmsPage.test.tsx @@ -4,8 +4,8 @@ import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; import { renderApp, mockAuthenticated, mockGuest } from '@/test/utils'; import { server } from '@/mocks/server'; -import { API_BASE } from '@/mocks/auth/fixtures'; -import { resetMockCmsInstances } from '@/mocks/cms/handlers'; +import { API_BASE } from '@/features/auth/mocks/fixtures'; +import { resetMockCmsInstances } from '@/features/cms/mocks/handlers'; import { _resetSetupStatusCache } from '@/router'; beforeEach(() => { diff --git a/frontend/src/pages/CmsPage.tsx b/frontend/src/features/cms/pages/CmsPage.tsx similarity index 87% rename from frontend/src/pages/CmsPage.tsx rename to frontend/src/features/cms/pages/CmsPage.tsx index f596f35..4b2a79e 100644 --- a/frontend/src/pages/CmsPage.tsx +++ b/frontend/src/features/cms/pages/CmsPage.tsx @@ -2,11 +2,11 @@ import { useState } from 'react'; import { LayoutGrid } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import { Button } from '@/components/ui/button'; -import { CmsInstanceList } from '@/components/cms/CmsInstanceList'; -import { AddCmsInstanceDialog } from '@/components/cms/AddCmsInstanceDialog'; -import { SetStatusDialog } from '@/components/cms/SetStatusDialog'; -import { useCmsInstances } from '@/api/useCmsInstances'; -import type { CmsInstance } from '@/api/types'; +import { CmsInstanceList } from '@/features/cms/components/CmsInstanceList'; +import { AddCmsInstanceDialog } from '@/features/cms/components/AddCmsInstanceDialog'; +import { SetStatusDialog } from '@/features/cms/components/SetStatusDialog'; +import { useCmsInstances } from '@/features/cms/services/useCmsInstances'; +import type { CmsInstance } from '@/features/cms/services/types'; export function CmsPage() { const { t } = useTranslation(); diff --git a/frontend/src/lib/schemas/cms.test.ts b/frontend/src/features/cms/schemas/cms.test.ts similarity index 100% rename from frontend/src/lib/schemas/cms.test.ts rename to frontend/src/features/cms/schemas/cms.test.ts diff --git a/frontend/src/lib/schemas/cms.ts b/frontend/src/features/cms/schemas/cms.ts similarity index 100% rename from frontend/src/lib/schemas/cms.ts rename to frontend/src/features/cms/schemas/cms.ts diff --git a/frontend/src/features/cms/services/types.ts b/frontend/src/features/cms/services/types.ts new file mode 100644 index 0000000..1c7a292 --- /dev/null +++ b/frontend/src/features/cms/services/types.ts @@ -0,0 +1,28 @@ +export type CmsInstanceStatus = 'Available' | 'NotAvailable' | 'Inactive'; + +export interface CmsInstance { + id: string; + name: string; + url: string; + status: CmsInstanceStatus; + disableMessage: string | null; + lastContactedAt: string | null; + lastStatusPushedAt: string | null; + lastIntegrityCheckFailedAt: string | null; +} + +export interface CreateCmsInstanceRequest { + name: string; + url: string; + apiKey: string; +} + +export interface UpdateCmsInstanceStatusRequest { + status: CmsInstanceStatus; + disableMessage: string | null; +} + +export interface UpdateStatusResult { + success: boolean; + slaveContactSuccess: boolean; +} diff --git a/frontend/src/api/useAddCmsInstance.test.ts b/frontend/src/features/cms/services/useAddCmsInstance.test.ts similarity index 96% rename from frontend/src/api/useAddCmsInstance.test.ts rename to frontend/src/features/cms/services/useAddCmsInstance.test.ts index 50d6f36..1ac0420 100644 --- a/frontend/src/api/useAddCmsInstance.test.ts +++ b/frontend/src/features/cms/services/useAddCmsInstance.test.ts @@ -4,7 +4,7 @@ import { http, HttpResponse } from 'msw'; import { createElement } from 'react'; import { describe, expect, it, vi } from 'vitest'; import { server } from '@/mocks/server'; -import { API_BASE } from '@/mocks/auth/fixtures'; +import { API_BASE } from '@/features/auth/mocks/fixtures'; import { useAddCmsInstance } from './useAddCmsInstance'; function createWrapper() { diff --git a/frontend/src/api/useAddCmsInstance.ts b/frontend/src/features/cms/services/useAddCmsInstance.ts similarity index 100% rename from frontend/src/api/useAddCmsInstance.ts rename to frontend/src/features/cms/services/useAddCmsInstance.ts diff --git a/frontend/src/api/useCmsInstances.test.ts b/frontend/src/features/cms/services/useCmsInstances.test.ts similarity index 93% rename from frontend/src/api/useCmsInstances.test.ts rename to frontend/src/features/cms/services/useCmsInstances.test.ts index 5f489d4..636f150 100644 --- a/frontend/src/api/useCmsInstances.test.ts +++ b/frontend/src/features/cms/services/useCmsInstances.test.ts @@ -4,8 +4,8 @@ import { http, HttpResponse } from 'msw'; import { createElement } from 'react'; import { describe, expect, it } from 'vitest'; import { server } from '@/mocks/server'; -import { API_BASE } from '@/mocks/auth/fixtures'; -import { getMockCmsInstances } from '@/mocks/cms/handlers'; +import { API_BASE } from '@/features/auth/mocks/fixtures'; +import { getMockCmsInstances } from '@/features/cms/mocks/handlers'; import { useCmsInstances } from './useCmsInstances'; function createWrapper() { diff --git a/frontend/src/api/useCmsInstances.ts b/frontend/src/features/cms/services/useCmsInstances.ts similarity index 100% rename from frontend/src/api/useCmsInstances.ts rename to frontend/src/features/cms/services/useCmsInstances.ts diff --git a/frontend/src/api/useUpdateCmsInstanceStatus.test.ts b/frontend/src/features/cms/services/useUpdateCmsInstanceStatus.test.ts similarity index 97% rename from frontend/src/api/useUpdateCmsInstanceStatus.test.ts rename to frontend/src/features/cms/services/useUpdateCmsInstanceStatus.test.ts index 6d99a07..cc65783 100644 --- a/frontend/src/api/useUpdateCmsInstanceStatus.test.ts +++ b/frontend/src/features/cms/services/useUpdateCmsInstanceStatus.test.ts @@ -4,7 +4,7 @@ import { http, HttpResponse } from 'msw'; import { createElement } from 'react'; import { describe, expect, it, vi } from 'vitest'; import { server } from '@/mocks/server'; -import { API_BASE } from '@/mocks/auth/fixtures'; +import { API_BASE } from '@/features/auth/mocks/fixtures'; import { useUpdateCmsInstanceStatus } from './useUpdateCmsInstanceStatus'; function createWrapper() { diff --git a/frontend/src/api/useUpdateCmsInstanceStatus.ts b/frontend/src/features/cms/services/useUpdateCmsInstanceStatus.ts similarity index 100% rename from frontend/src/api/useUpdateCmsInstanceStatus.ts rename to frontend/src/features/cms/services/useUpdateCmsInstanceStatus.ts diff --git a/frontend/src/pages/DashboardPage.test.tsx b/frontend/src/features/dashboard/pages/DashboardPage.test.tsx similarity index 100% rename from frontend/src/pages/DashboardPage.test.tsx rename to frontend/src/features/dashboard/pages/DashboardPage.test.tsx diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/features/dashboard/pages/DashboardPage.tsx similarity index 90% rename from frontend/src/pages/DashboardPage.tsx rename to frontend/src/features/dashboard/pages/DashboardPage.tsx index c29ed3a..f22a30b 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/features/dashboard/pages/DashboardPage.tsx @@ -1,9 +1,9 @@ import { useTranslation } from 'react-i18next'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; -import { useAuth } from '@/contexts/auth-context'; -import { useAvailabilityStatus } from '@/api/useAvailability'; -import { AvailabilityStatusBadge } from '@/components/shared/AvailabilityStatusBadge'; +import { useAuth } from '@/features/auth/context/auth-context'; +import { useAvailabilityStatus } from '@/features/availability/services/useAvailability'; +import { AvailabilityStatusBadge } from '@/features/availability/components/AvailabilityStatusBadge'; export function DashboardPage() { const { t } = useTranslation(); diff --git a/frontend/src/components/users/InviteUserDialog.test.tsx b/frontend/src/features/invitation/components/InviteUserDialog.test.tsx similarity index 98% rename from frontend/src/components/users/InviteUserDialog.test.tsx rename to frontend/src/features/invitation/components/InviteUserDialog.test.tsx index 46f79a2..f59bf99 100644 --- a/frontend/src/components/users/InviteUserDialog.test.tsx +++ b/frontend/src/features/invitation/components/InviteUserDialog.test.tsx @@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; import { renderApp, mockAuthenticated } from '@/test/utils'; import { server } from '@/mocks/server'; -import { API_BASE } from '@/mocks/auth/fixtures'; +import { API_BASE } from '@/features/auth/mocks/fixtures'; import { _resetSetupStatusCache } from '@/router'; beforeEach(() => { diff --git a/frontend/src/components/users/InviteUserDialog.tsx b/frontend/src/features/invitation/components/InviteUserDialog.tsx similarity index 97% rename from frontend/src/components/users/InviteUserDialog.tsx rename to frontend/src/features/invitation/components/InviteUserDialog.tsx index ba62193..69724ad 100644 --- a/frontend/src/components/users/InviteUserDialog.tsx +++ b/frontend/src/features/invitation/components/InviteUserDialog.tsx @@ -21,8 +21,8 @@ import { } from '@/components/ui/select'; import { FormErrorBanner } from '@/components/ui/FormErrorBanner'; import { FieldError } from '@/components/ui/FieldError'; -import { useInviteUser } from '@/api/useUsers'; -import { inviteUserSchema, type InviteUserFormData } from '@/lib/schemas/users'; +import { useInviteUser } from '@/features/users/services/useUsers'; +import { inviteUserSchema, type InviteUserFormData } from '@/features/users/schemas/users'; import { NetworkError } from '@/lib/api-client'; interface InviteUserDialogProps { diff --git a/frontend/src/mocks/invitation/handlers.ts b/frontend/src/features/invitation/mocks/handlers.ts similarity index 96% rename from frontend/src/mocks/invitation/handlers.ts rename to frontend/src/features/invitation/mocks/handlers.ts index 6493a2d..aeb6621 100644 --- a/frontend/src/mocks/invitation/handlers.ts +++ b/frontend/src/features/invitation/mocks/handlers.ts @@ -1,5 +1,5 @@ import { http, HttpResponse } from 'msw'; -import { API_BASE } from '../auth/fixtures'; +import { API_BASE } from '@/features/auth/mocks/fixtures'; export const invitationHandlers = [ http.get(`${API_BASE}/api/v1/Invitation/validate`, ({ request }) => { diff --git a/frontend/src/pages/InviteCompletePage.test.tsx b/frontend/src/features/invitation/pages/InviteCompletePage.test.tsx similarity index 100% rename from frontend/src/pages/InviteCompletePage.test.tsx rename to frontend/src/features/invitation/pages/InviteCompletePage.test.tsx diff --git a/frontend/src/pages/InviteCompletePage.tsx b/frontend/src/features/invitation/pages/InviteCompletePage.tsx similarity index 99% rename from frontend/src/pages/InviteCompletePage.tsx rename to frontend/src/features/invitation/pages/InviteCompletePage.tsx index 6e9bf03..285cc30 100644 --- a/frontend/src/pages/InviteCompletePage.tsx +++ b/frontend/src/features/invitation/pages/InviteCompletePage.tsx @@ -7,10 +7,10 @@ import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; -import { invitationCompleteFormSchema, type InvitationCompleteFormData } from '@/lib/schemas/auth'; +import { invitationCompleteFormSchema, type InvitationCompleteFormData } from '@/features/auth/schemas/auth'; import { FormErrorBanner } from '@/components/ui/FormErrorBanner'; import { FieldError } from '@/components/ui/FieldError'; -import { useValidateInvitation, useCompleteInvitation } from '@/api/useInvitation'; +import { useValidateInvitation, useCompleteInvitation } from '@/features/invitation/services/useInvitation'; import { ProblemDetailsError, NetworkError } from '@/lib/api-client'; interface FormError { diff --git a/frontend/src/features/invitation/services/types.ts b/frontend/src/features/invitation/services/types.ts new file mode 100644 index 0000000..df34cb1 --- /dev/null +++ b/frontend/src/features/invitation/services/types.ts @@ -0,0 +1,12 @@ +export interface InvitationValidation { + email: string; + name: string | null; + isValid: boolean; + errorCode: 'EXPIRED' | 'USED' | 'NOT_FOUND' | null; +} + +export interface InviteCompleteRequest { + token: string; + name: string; + password: string; +} diff --git a/frontend/src/api/useInvitation.test.ts b/frontend/src/features/invitation/services/useInvitation.test.ts similarity index 97% rename from frontend/src/api/useInvitation.test.ts rename to frontend/src/features/invitation/services/useInvitation.test.ts index 8e5fd68..232563e 100644 --- a/frontend/src/api/useInvitation.test.ts +++ b/frontend/src/features/invitation/services/useInvitation.test.ts @@ -4,8 +4,8 @@ import { createElement } from 'react'; import { http, HttpResponse } from 'msw'; import { describe, expect, it } from 'vitest'; import { server } from '@/mocks/server'; -import { API_BASE } from '@/mocks/auth/fixtures'; -import { useValidateInvitation, useCompleteInvitation } from '@/api/useInvitation'; +import { API_BASE } from '@/features/auth/mocks/fixtures'; +import { useValidateInvitation, useCompleteInvitation } from '@/features/invitation/services/useInvitation'; function createWrapper() { const queryClient = new QueryClient({ diff --git a/frontend/src/api/useInvitation.ts b/frontend/src/features/invitation/services/useInvitation.ts similarity index 100% rename from frontend/src/api/useInvitation.ts rename to frontend/src/features/invitation/services/useInvitation.ts diff --git a/frontend/src/pages/ProfilePage.test.tsx b/frontend/src/features/profile/pages/ProfilePage.test.tsx similarity index 98% rename from frontend/src/pages/ProfilePage.test.tsx rename to frontend/src/features/profile/pages/ProfilePage.test.tsx index ca7484b..2a201a2 100644 --- a/frontend/src/pages/ProfilePage.test.tsx +++ b/frontend/src/features/profile/pages/ProfilePage.test.tsx @@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; import { renderApp, mockAuthenticated, mockGuest } from '@/test/utils'; import { server } from '@/mocks/server'; -import { API_BASE } from '@/mocks/auth/fixtures'; +import { API_BASE } from '@/features/auth/mocks/fixtures'; import { _resetSetupStatusCache } from '@/router'; beforeEach(() => { diff --git a/frontend/src/pages/ProfilePage.tsx b/frontend/src/features/profile/pages/ProfilePage.tsx similarity index 98% rename from frontend/src/pages/ProfilePage.tsx rename to frontend/src/features/profile/pages/ProfilePage.tsx index 00b0225..92d5b17 100644 --- a/frontend/src/pages/ProfilePage.tsx +++ b/frontend/src/features/profile/pages/ProfilePage.tsx @@ -4,8 +4,8 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { useTranslation } from 'react-i18next'; import { toast } from 'sonner'; -import { useAuth } from '@/contexts/auth-context'; -import { useUpdateProfile, useChangePassword } from '@/api/useProfile'; +import { useAuth } from '@/features/auth/context/auth-context'; +import { useUpdateProfile, useChangePassword } from '@/features/profile/services/useProfile'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; diff --git a/frontend/src/api/useProfile.test.ts b/frontend/src/features/profile/services/useProfile.test.ts similarity index 93% rename from frontend/src/api/useProfile.test.ts rename to frontend/src/features/profile/services/useProfile.test.ts index a62670f..7d08a7e 100644 --- a/frontend/src/api/useProfile.test.ts +++ b/frontend/src/features/profile/services/useProfile.test.ts @@ -4,9 +4,9 @@ import { createElement } from 'react'; import { http, HttpResponse } from 'msw'; import { describe, expect, it } from 'vitest'; import { server } from '@/mocks/server'; -import { API_BASE } from '@/mocks/auth/fixtures'; -import { useUpdateProfile, useChangePassword } from '@/api/useProfile'; -import { AuthProvider } from '@/contexts/AuthProvider'; +import { API_BASE } from '@/features/auth/mocks/fixtures'; +import { useUpdateProfile, useChangePassword } from '@/features/profile/services/useProfile'; +import { AuthProvider } from '@/features/auth/context/AuthProvider'; import i18n from '@/i18n/config'; import { I18nextProvider } from 'react-i18next'; import type { ReactNode } from 'react'; diff --git a/frontend/src/api/useProfile.ts b/frontend/src/features/profile/services/useProfile.ts similarity index 86% rename from frontend/src/api/useProfile.ts rename to frontend/src/features/profile/services/useProfile.ts index d388dbf..df71400 100644 --- a/frontend/src/api/useProfile.ts +++ b/frontend/src/features/profile/services/useProfile.ts @@ -1,7 +1,7 @@ import { useMutation } from '@tanstack/react-query'; import { api } from '@/lib/api-client'; -import { useAuth } from '@/contexts/auth-context'; -import type { UserListItem } from './types'; +import { useAuth } from '@/features/auth/context/auth-context'; +import type { UserListItem } from '@/features/users/services/types'; export interface UpdateProfilePayload { name: string; diff --git a/frontend/src/pages/SettingsPage.test.tsx b/frontend/src/features/settings/pages/SettingsPage.test.tsx similarity index 98% rename from frontend/src/pages/SettingsPage.test.tsx rename to frontend/src/features/settings/pages/SettingsPage.test.tsx index c8e3331..fecb67b 100644 --- a/frontend/src/pages/SettingsPage.test.tsx +++ b/frontend/src/features/settings/pages/SettingsPage.test.tsx @@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; import { renderApp, mockAuthenticated, mockGuest } from '@/test/utils'; import { server } from '@/mocks/server'; -import { API_BASE } from '@/mocks/auth/fixtures'; +import { API_BASE } from '@/features/auth/mocks/fixtures'; import { _resetSetupStatusCache } from '@/router'; beforeEach(() => { diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/features/settings/pages/SettingsPage.tsx similarity index 96% rename from frontend/src/pages/SettingsPage.tsx rename to frontend/src/features/settings/pages/SettingsPage.tsx index 641edff..deae536 100644 --- a/frontend/src/pages/SettingsPage.tsx +++ b/frontend/src/features/settings/pages/SettingsPage.tsx @@ -2,13 +2,13 @@ import { useState } from 'react'; import { useTranslation } from 'react-i18next'; import { toast } from 'sonner'; import { Lock } from 'lucide-react'; -import { useAvailabilityStatus, useUpdateAvailability } from '@/api/useAvailability'; -import { AvailabilityStatusBadge } from '@/components/shared/AvailabilityStatusBadge'; +import { useAvailabilityStatus, useUpdateAvailability } from '@/features/availability/services/useAvailability'; +import { AvailabilityStatusBadge } from '@/features/availability/components/AvailabilityStatusBadge'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Label } from '@/components/ui/label'; import { ProblemDetailsError } from '@/lib/api-client'; -import type { AvailabilityStatus } from '@/api/types'; +import type { AvailabilityStatus } from '@/features/availability/services/types'; function PlaceholderCard({ titleKey, comingSoonKey }: { titleKey: string; comingSoonKey: string }) { const { t } = useTranslation(); diff --git a/frontend/src/mocks/setup/handlers.ts b/frontend/src/features/setup/mocks/handlers.ts similarity index 94% rename from frontend/src/mocks/setup/handlers.ts rename to frontend/src/features/setup/mocks/handlers.ts index dcd717f..675f4e7 100644 --- a/frontend/src/mocks/setup/handlers.ts +++ b/frontend/src/features/setup/mocks/handlers.ts @@ -1,6 +1,6 @@ import { http, HttpResponse } from 'msw'; -import type { SetupStatus } from '@/api/types'; -import { API_BASE } from '../auth/fixtures'; +import type { SetupStatus } from '@/features/setup/services/types'; +import { API_BASE } from '@/features/auth/mocks/fixtures'; /** Setup status mock (used by public bootstrap guards). */ export const setupHandlers = [ diff --git a/frontend/src/pages/SetupPage.test.tsx b/frontend/src/features/setup/pages/SetupPage.test.tsx similarity index 100% rename from frontend/src/pages/SetupPage.test.tsx rename to frontend/src/features/setup/pages/SetupPage.test.tsx diff --git a/frontend/src/pages/SetupPage.tsx b/frontend/src/features/setup/pages/SetupPage.tsx similarity index 98% rename from frontend/src/pages/SetupPage.tsx rename to frontend/src/features/setup/pages/SetupPage.tsx index 4352f82..e277c57 100644 --- a/frontend/src/pages/SetupPage.tsx +++ b/frontend/src/features/setup/pages/SetupPage.tsx @@ -7,10 +7,10 @@ import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; -import { setupFormSchema, type SetupFormData } from '@/lib/schemas/auth'; +import { setupFormSchema, type SetupFormData } from '@/features/auth/schemas/auth'; import { FormErrorBanner } from '@/components/ui/FormErrorBanner'; import { FieldError } from '@/components/ui/FieldError'; -import { useSetup } from '@/api/useSetup'; +import { useSetup } from '@/features/setup/services/useSetup'; import { ProblemDetailsError, NetworkError } from '@/lib/api-client'; import { changeLanguage } from '@/i18n/config'; import { _markSystemInitialized } from '@/router'; diff --git a/frontend/src/features/setup/services/types.ts b/frontend/src/features/setup/services/types.ts new file mode 100644 index 0000000..649d21c --- /dev/null +++ b/frontend/src/features/setup/services/types.ts @@ -0,0 +1,10 @@ +// Setup status (used by guards during bootstrap). +export interface SetupStatus { + initialized: boolean; +} + +export interface SetupRequest { + name: string; + email: string; + password: string; +} diff --git a/frontend/src/auth/useInitGuard.test.ts b/frontend/src/features/setup/services/useInitGuard.test.ts similarity index 94% rename from frontend/src/auth/useInitGuard.test.ts rename to frontend/src/features/setup/services/useInitGuard.test.ts index d227c4f..d7d4fd3 100644 --- a/frontend/src/auth/useInitGuard.test.ts +++ b/frontend/src/features/setup/services/useInitGuard.test.ts @@ -2,8 +2,8 @@ import { renderHook, waitFor } from '@testing-library/react'; import { http, HttpResponse } from 'msw'; import { beforeEach, describe, expect, it } from 'vitest'; import { server } from '@/mocks/server'; -import { API_BASE } from '@/mocks/auth/fixtures'; -import { useInitGuard, _resetSetupStatusCache } from '@/auth/useInitGuard'; +import { API_BASE } from '@/features/auth/mocks/fixtures'; +import { useInitGuard, _resetSetupStatusCache } from '@/features/setup/services/useInitGuard'; const SETUP_STATUS_URL = `${API_BASE}/Setup/status`; diff --git a/frontend/src/auth/useInitGuard.ts b/frontend/src/features/setup/services/useInitGuard.ts similarity index 97% rename from frontend/src/auth/useInitGuard.ts rename to frontend/src/features/setup/services/useInitGuard.ts index eed7485..ccaf115 100644 --- a/frontend/src/auth/useInitGuard.ts +++ b/frontend/src/features/setup/services/useInitGuard.ts @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { api } from '../lib/api-client'; +import { api } from '@/lib/api-client'; interface SetupStatus { initialized: boolean; diff --git a/frontend/src/api/useSetup.test.ts b/frontend/src/features/setup/services/useSetup.test.ts similarity index 95% rename from frontend/src/api/useSetup.test.ts rename to frontend/src/features/setup/services/useSetup.test.ts index fe93038..82bfcce 100644 --- a/frontend/src/api/useSetup.test.ts +++ b/frontend/src/features/setup/services/useSetup.test.ts @@ -2,8 +2,8 @@ import { act, renderHook } from '@testing-library/react'; import { http, HttpResponse } from 'msw'; import { describe, expect, it } from 'vitest'; import { server } from '@/mocks/server'; -import { API_BASE } from '@/mocks/auth/fixtures'; -import { useSetup } from '@/api/useSetup'; +import { API_BASE } from '@/features/auth/mocks/fixtures'; +import { useSetup } from '@/features/setup/services/useSetup'; const validSetupData = { name: 'Test Owner', diff --git a/frontend/src/api/useSetup.ts b/frontend/src/features/setup/services/useSetup.ts similarity index 96% rename from frontend/src/api/useSetup.ts rename to frontend/src/features/setup/services/useSetup.ts index a4478c6..04250d0 100644 --- a/frontend/src/api/useSetup.ts +++ b/frontend/src/features/setup/services/useSetup.ts @@ -1,5 +1,5 @@ import { useState, useCallback } from 'react'; -import { api } from '../lib/api-client'; +import { api } from '@/lib/api-client'; interface SetupRequest { name: string; diff --git a/frontend/src/components/auth/ModuleGuard.tsx b/frontend/src/features/system/components/ModuleGuard.tsx similarity index 94% rename from frontend/src/components/auth/ModuleGuard.tsx rename to frontend/src/features/system/components/ModuleGuard.tsx index 3349c60..dac9568 100644 --- a/frontend/src/components/auth/ModuleGuard.tsx +++ b/frontend/src/features/system/components/ModuleGuard.tsx @@ -1,7 +1,7 @@ import type { ReactNode } from 'react'; import { Link } from '@tanstack/react-router'; import { useTranslation } from 'react-i18next'; -import { useSystemCapabilities } from '@/api/useSystemCapabilities'; +import { useSystemCapabilities } from '@/features/system/services/useSystemCapabilities'; interface ModuleGuardProps { requiredModule: string; diff --git a/frontend/src/mocks/system/handlers.ts b/frontend/src/features/system/mocks/handlers.ts similarity index 100% rename from frontend/src/mocks/system/handlers.ts rename to frontend/src/features/system/mocks/handlers.ts diff --git a/frontend/src/features/system/services/types.ts b/frontend/src/features/system/services/types.ts new file mode 100644 index 0000000..a36dc7f --- /dev/null +++ b/frontend/src/features/system/services/types.ts @@ -0,0 +1,6 @@ +// Which optional modules (e.g. "Master") this backend instance has loaded — +// lets the frontend tell a master-only feature apart from a slave instance +// without that module (local master/slave dev setup). +export interface SystemCapabilities { + modules: string[]; +} diff --git a/frontend/src/api/useSystemCapabilities.ts b/frontend/src/features/system/services/useSystemCapabilities.ts similarity index 100% rename from frontend/src/api/useSystemCapabilities.ts rename to frontend/src/features/system/services/useSystemCapabilities.ts diff --git a/frontend/src/components/users/DeleteUserDialog.tsx b/frontend/src/features/users/components/DeleteUserDialog.tsx similarity index 96% rename from frontend/src/components/users/DeleteUserDialog.tsx rename to frontend/src/features/users/components/DeleteUserDialog.tsx index a12a911..697cf96 100644 --- a/frontend/src/components/users/DeleteUserDialog.tsx +++ b/frontend/src/features/users/components/DeleteUserDialog.tsx @@ -8,7 +8,7 @@ import { DialogFooter, DialogDescription, } from '@/components/ui/dialog'; -import type { UserListItem } from '@/api/types'; +import type { UserListItem } from '@/features/users/services/types'; interface DeleteUserDialogProps { user: UserListItem | null; diff --git a/frontend/src/mocks/users/handlers.ts b/frontend/src/features/users/mocks/handlers.ts similarity index 96% rename from frontend/src/mocks/users/handlers.ts rename to frontend/src/features/users/mocks/handlers.ts index 7b801e4..d71368b 100644 --- a/frontend/src/mocks/users/handlers.ts +++ b/frontend/src/features/users/mocks/handlers.ts @@ -1,6 +1,6 @@ import { http, HttpResponse } from 'msw'; -import type { UserListItem } from '@/api/types'; -import { API_BASE, mockUser } from '../auth/fixtures'; +import type { UserListItem } from '@/features/users/services/types'; +import { API_BASE, mockUser } from '@/features/auth/mocks/fixtures'; let mockUsers: UserListItem[] = [ { diff --git a/frontend/src/pages/UsersPage.test.tsx b/frontend/src/features/users/pages/UsersPage.test.tsx similarity index 95% rename from frontend/src/pages/UsersPage.test.tsx rename to frontend/src/features/users/pages/UsersPage.test.tsx index 5d4a7dd..a49689a 100644 --- a/frontend/src/pages/UsersPage.test.tsx +++ b/frontend/src/features/users/pages/UsersPage.test.tsx @@ -4,8 +4,8 @@ import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; import { renderApp, mockAuthenticated } from '@/test/utils'; import { server } from '@/mocks/server'; -import { API_BASE } from '@/mocks/auth/fixtures'; -import { getMockUsers } from '@/mocks/users/handlers'; +import { API_BASE } from '@/features/auth/mocks/fixtures'; +import { getMockUsers } from '@/features/users/mocks/handlers'; import { _resetSetupStatusCache } from '@/router'; beforeEach(() => { diff --git a/frontend/src/pages/UsersPage.tsx b/frontend/src/features/users/pages/UsersPage.tsx similarity index 98% rename from frontend/src/pages/UsersPage.tsx rename to frontend/src/features/users/pages/UsersPage.tsx index de87cb6..bf43094 100644 --- a/frontend/src/pages/UsersPage.tsx +++ b/frontend/src/features/users/pages/UsersPage.tsx @@ -31,14 +31,15 @@ import { DropdownMenuSubTrigger, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import { InviteUserDialog } from '@/components/users/InviteUserDialog'; -import { DeleteUserDialog } from '@/components/users/DeleteUserDialog'; +import { InviteUserDialog } from '@/features/invitation/components/InviteUserDialog'; +import { DeleteUserDialog } from '@/features/users/components/DeleteUserDialog'; import { RoleBadge } from '@/components/shared/RoleBadge'; -import { useUsers, useChangeRole, useSetUserActive, useDeleteUser } from '@/api/useUsers'; -import { useAuth } from '@/contexts/auth-context'; +import { useUsers, useChangeRole, useSetUserActive, useDeleteUser } from '@/features/users/services/useUsers'; +import { useAuth } from '@/features/auth/context/auth-context'; import { Trans } from 'react-i18next'; import { ProblemDetailsError } from '@/lib/api-client'; -import type { UserRole, UserListItem } from '@/api/types'; +import type { UserRole } from '@/features/auth/services/types'; +import type { UserListItem } from '@/features/users/services/types'; function statusBadgeVariant(user: UserListItem) { if (user.invitationPending) return 'outline'; diff --git a/frontend/src/lib/schemas/users.test.ts b/frontend/src/features/users/schemas/users.test.ts similarity index 100% rename from frontend/src/lib/schemas/users.test.ts rename to frontend/src/features/users/schemas/users.test.ts diff --git a/frontend/src/lib/schemas/users.ts b/frontend/src/features/users/schemas/users.ts similarity index 100% rename from frontend/src/lib/schemas/users.ts rename to frontend/src/features/users/schemas/users.ts diff --git a/frontend/src/features/users/services/types.ts b/frontend/src/features/users/services/types.ts new file mode 100644 index 0000000..5fcc541 --- /dev/null +++ b/frontend/src/features/users/services/types.ts @@ -0,0 +1,20 @@ +import type { User, UserRole } from '@/features/auth/services/types'; + +export interface UserListItem extends User { + createdAt: string; + invitationPending: boolean; + inviteLink: string | null; +} + +export interface InviteUserPayload { + email: string; + role: Exclude; +} + +export interface InviteUserResponse { + inviteLink: string; +} + +export interface ChangeRolePayload { + newRole: UserRole; +} diff --git a/frontend/src/api/useUsers.test.ts b/frontend/src/features/users/services/useUsers.test.ts similarity index 97% rename from frontend/src/api/useUsers.test.ts rename to frontend/src/features/users/services/useUsers.test.ts index ec51b14..0992571 100644 --- a/frontend/src/api/useUsers.test.ts +++ b/frontend/src/features/users/services/useUsers.test.ts @@ -4,8 +4,8 @@ import { http, HttpResponse } from 'msw'; import { createElement } from 'react'; import { describe, expect, it } from 'vitest'; import { server } from '@/mocks/server'; -import { API_BASE } from '@/mocks/auth/fixtures'; -import { getMockUsers } from '@/mocks/users/handlers'; +import { API_BASE } from '@/features/auth/mocks/fixtures'; +import { getMockUsers } from '@/features/users/mocks/handlers'; import { useUsers, useInviteUser, useChangeRole, useSetUserActive, useDeleteUser } from './useUsers'; function createWrapper() { diff --git a/frontend/src/api/useUsers.ts b/frontend/src/features/users/services/useUsers.ts similarity index 95% rename from frontend/src/api/useUsers.ts rename to frontend/src/features/users/services/useUsers.ts index fcdc48a..6e0faf1 100644 --- a/frontend/src/api/useUsers.ts +++ b/frontend/src/features/users/services/useUsers.ts @@ -1,6 +1,7 @@ import { useQuery, useMutation, useQueryClient, type UseMutationOptions } from '@tanstack/react-query'; import { api } from '@/lib/api-client'; -import type { UserListItem, InviteUserPayload, InviteUserResponse, ChangeRolePayload, UserRole } from './types'; +import type { UserListItem, InviteUserPayload, InviteUserResponse, ChangeRolePayload } from './types'; +import type { UserRole } from '@/features/auth/services/types'; export function useUsers() { return useQuery({ diff --git a/frontend/src/lib/api-client.ts b/frontend/src/lib/api-client.ts index 8218a39..bcdea9e 100644 --- a/frontend/src/lib/api-client.ts +++ b/frontend/src/lib/api-client.ts @@ -1,6 +1,19 @@ -import type { ProblemDetails } from '@/api/types'; import { getAppConfig } from '@/lib/config'; +// RFC 9457 ProblemDetails for standardized error handling. +export interface ProblemDetails { + type?: string; + title?: string; + status?: number; + detail?: string; + instance?: string; + // Extension members (e.g. traceId, errors). + [extension: string]: unknown; +} + +// Convenience wrapper for API results (optional usage). +export type ApiResult = { ok: true; data: T } | { ok: false; error: ProblemDetails }; + /** * Typed error thrown for any non-2xx API response. Wraps an RFC 9457 * ProblemDetails body so callers can present context-aware messages diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 3f85b51..e1f5e1c 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -4,8 +4,8 @@ import { RouterProvider } from '@tanstack/react-router'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import './index.css'; import './i18n/config'; -import { AuthProvider } from '@/contexts/AuthProvider'; -import { useAuth } from '@/contexts/auth-context'; +import { AuthProvider } from '@/features/auth/context/AuthProvider'; +import { useAuth } from '@/features/auth/context/auth-context'; import { Toaster } from '@/components/ui/sonner'; import { router } from '@/router'; import { getAppConfig } from '@/lib/config'; diff --git a/frontend/src/mocks/index.ts b/frontend/src/mocks/index.ts index a0ed9fc..af0998c 100644 --- a/frontend/src/mocks/index.ts +++ b/frontend/src/mocks/index.ts @@ -1,19 +1,19 @@ -import { authHandlers } from './auth/handlers'; -import { userHandlers } from './users/handlers'; -import { setupHandlers } from './setup/handlers'; -import { invitationHandlers } from './invitation/handlers'; -import { availabilityHandlers } from './availability/handlers'; -import { cmsHandlers } from './cms/handlers'; -import { systemHandlers } from './system/handlers'; +import { authHandlers } from '@/features/auth/mocks/handlers'; +import { userHandlers } from '@/features/users/mocks/handlers'; +import { setupHandlers } from '@/features/setup/mocks/handlers'; +import { invitationHandlers } from '@/features/invitation/mocks/handlers'; +import { availabilityHandlers } from '@/features/availability/mocks/handlers'; +import { cmsHandlers } from '@/features/cms/mocks/handlers'; +import { systemHandlers } from '@/features/system/mocks/handlers'; /** All default MSW handlers, composed from feature folders (Q3-B). */ export const handlers = [...authHandlers, ...userHandlers, ...setupHandlers, ...invitationHandlers, ...availabilityHandlers, ...cmsHandlers, ...systemHandlers]; -export { authHandlers } from './auth/handlers'; -export { userHandlers } from './users/handlers'; -export { setupHandlers, setupUninitializedHandlers, setupConflictHandlers, setupNetworkErrorHandlers } from './setup/handlers'; -export { invitationHandlers } from './invitation/handlers'; -export { availabilityHandlers } from './availability/handlers'; -export { cmsHandlers, resetMockCmsInstances, getMockCmsInstances } from './cms/handlers'; -export { systemHandlers } from './system/handlers'; -export * from './auth/fixtures'; +export { authHandlers } from '@/features/auth/mocks/handlers'; +export { userHandlers } from '@/features/users/mocks/handlers'; +export { setupHandlers, setupUninitializedHandlers, setupConflictHandlers, setupNetworkErrorHandlers } from '@/features/setup/mocks/handlers'; +export { invitationHandlers } from '@/features/invitation/mocks/handlers'; +export { availabilityHandlers } from '@/features/availability/mocks/handlers'; +export { cmsHandlers, resetMockCmsInstances, getMockCmsInstances } from '@/features/cms/mocks/handlers'; +export { systemHandlers } from '@/features/system/mocks/handlers'; +export * from '@/features/auth/mocks/fixtures'; diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index 85b9a00..947dffe 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -6,14 +6,14 @@ import { Outlet, redirect, } from '@tanstack/react-router'; -import type { AuthContextValue } from '@/contexts/auth-context'; -import type { SetupStatus } from '@/api/types'; +import type { AuthContextValue } from '@/features/auth/context/auth-context'; +import type { SetupStatus } from '@/features/setup/services/types'; import { api, NetworkError } from '@/lib/api-client'; import { AppLayout } from '@/components/layout/AppLayout'; -import { LoginPage } from '@/pages/LoginPage'; +import { LoginPage } from '@/features/auth/pages/LoginPage'; import { NotFoundPage } from '@/pages/NotFoundPage'; -import { RoleGuard } from '@/components/auth/RoleGuard'; -import { ModuleGuard } from '@/components/auth/ModuleGuard'; +import { RoleGuard } from '@/features/auth/components/RoleGuard'; +import { ModuleGuard } from '@/features/system/components/ModuleGuard'; export interface RouterContext { auth: AuthContextValue; @@ -146,7 +146,7 @@ const loginRoute = createRoute({ const setupRoute = createRoute({ getParentRoute: () => rootRoute, path: '/setup', - component: lazyPage(() => import('@/pages/SetupPage'), 'SetupPage'), + component: lazyPage(() => import('@/features/setup/pages/SetupPage'), 'SetupPage'), }); const inviteCompleteRoute = createRoute({ @@ -155,7 +155,7 @@ const inviteCompleteRoute = createRoute({ validateSearch: (search: Record): { token?: string } => ({ token: typeof search.token === 'string' ? search.token : undefined, }), - component: lazyPage(() => import('@/pages/InviteCompletePage'), 'InviteCompletePage'), + component: lazyPage(() => import('@/features/invitation/pages/InviteCompletePage'), 'InviteCompletePage'), }); const authenticatedRoute = createRoute({ @@ -172,7 +172,7 @@ const authenticatedRoute = createRoute({ const dashboardRoute = createRoute({ getParentRoute: () => authenticatedRoute, path: '/dashboard', - component: lazyPage(() => import('@/pages/DashboardPage'), 'DashboardPage'), + component: lazyPage(() => import('@/features/dashboard/pages/DashboardPage'), 'DashboardPage'), }); const usersRoute = createRoute({ @@ -180,7 +180,7 @@ const usersRoute = createRoute({ path: '/users', component: () => ( - {lazyPage(() => import('@/pages/UsersPage'), 'UsersPage')()} + {lazyPage(() => import('@/features/users/pages/UsersPage'), 'UsersPage')()} ), }); @@ -191,7 +191,7 @@ const cmsRoute = createRoute({ component: () => ( - {lazyPage(() => import('@/pages/CmsPage'), 'CmsPage')()} + {lazyPage(() => import('@/features/cms/pages/CmsPage'), 'CmsPage')()} ), @@ -202,7 +202,7 @@ const settingsRoute = createRoute({ path: '/settings', component: () => ( - {lazyPage(() => import('@/pages/SettingsPage'), 'SettingsPage')()} + {lazyPage(() => import('@/features/settings/pages/SettingsPage'), 'SettingsPage')()} ), }); @@ -210,7 +210,7 @@ const settingsRoute = createRoute({ const profileRoute = createRoute({ getParentRoute: () => authenticatedRoute, path: '/profile', - component: lazyPage(() => import('@/pages/ProfilePage'), 'ProfilePage'), + component: lazyPage(() => import('@/features/profile/pages/ProfilePage'), 'ProfilePage'), }); const accessDeniedRoute = createRoute({ diff --git a/frontend/src/test/utils.tsx b/frontend/src/test/utils.tsx index 6bea3bf..e9a34dd 100644 --- a/frontend/src/test/utils.tsx +++ b/frontend/src/test/utils.tsx @@ -5,12 +5,12 @@ import { http, HttpResponse } from 'msw'; import { createMemoryHistory, createRouter, RouterProvider } from '@tanstack/react-router'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { I18nextProvider } from 'react-i18next'; -import { AuthProvider } from '@/contexts/AuthProvider'; -import { useAuth } from '@/contexts/auth-context'; +import { AuthProvider } from '@/features/auth/context/AuthProvider'; +import { useAuth } from '@/features/auth/context/auth-context'; import { Toaster } from '@/components/ui/sonner'; import { routeTree } from '@/router'; import { server } from '@/mocks/server'; -import { API_BASE, makeAuthResponse } from '@/mocks/auth/fixtures'; +import { API_BASE, makeAuthResponse } from '@/features/auth/mocks/fixtures'; import i18n from '@/i18n/config'; function createTestQueryClient() { -- 2.39.5