Merge pull request 'Restructure frontend' (#8) from feature/frontend-feature-based-structure into master
Continuous Integration / config (push) Successful in 11s
Continuous Integration / changes (push) Successful in 21s
Continuous Integration / backend-build (push) Skipped
Continuous Integration / backend-test (push) Skipped
Continuous Integration / vulnerability-scan (push) Skipped
Continuous Integration / frontend-prepare (push) Successful in 1m42s
Continuous Integration / frontend-build (push) Successful in 2m8s
Continuous Integration / frontend-test (push) Successful in 4m37s
Continuous Integration / frontend-lint (push) Successful in 1m58s
Continuous Integration / publish-production (push) Skipped
Continuous Integration / deploy-production (push) Skipped
Continuous Integration / publish-test (push) Successful in 6m50s
Deploy (SCP) / deploy (push) Successful in 1m31s
Continuous Integration / deploy-test (push) Successful in 1m32s
Continuous Integration / config (push) Successful in 11s
Continuous Integration / changes (push) Successful in 21s
Continuous Integration / backend-build (push) Skipped
Continuous Integration / backend-test (push) Skipped
Continuous Integration / vulnerability-scan (push) Skipped
Continuous Integration / frontend-prepare (push) Successful in 1m42s
Continuous Integration / frontend-build (push) Successful in 2m8s
Continuous Integration / frontend-test (push) Successful in 4m37s
Continuous Integration / frontend-lint (push) Successful in 1m58s
Continuous Integration / publish-production (push) Skipped
Continuous Integration / deploy-production (push) Skipped
Continuous Integration / publish-test (push) Successful in 6m50s
Deploy (SCP) / deploy (push) Successful in 1m31s
Continuous Integration / deploy-test (push) Successful in 1m32s
Reviewed-on: #8
This commit was merged in pull request #8.
This commit is contained in:
@@ -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<T> = { 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<UserRole, 'Owner'>;
|
||||
}
|
||||
|
||||
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[];
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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';
|
||||
|
||||
+1
-1
@@ -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(() => {
|
||||
+2
-2
@@ -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[];
|
||||
+3
-3
@@ -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';
|
||||
|
||||
+2
-2
@@ -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
|
||||
+1
-1
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
@@ -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 {
|
||||
+1
-1
@@ -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 () => {
|
||||
@@ -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();
|
||||
+1
-1
@@ -4,7 +4,7 @@ import {
|
||||
setupFormSchema,
|
||||
loginFormSchema,
|
||||
invitationCompleteFormSchema,
|
||||
} from '@/lib/schemas/auth';
|
||||
} from '@/features/auth/schemas/auth';
|
||||
|
||||
describe('passwordSchema', () => {
|
||||
it('accepts a valid password', () => {
|
||||
@@ -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;
|
||||
}
|
||||
+1
-1
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
+1
-1
@@ -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() {
|
||||
+2
-2
@@ -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(() => {
|
||||
+2
-2
@@ -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;
|
||||
+1
-1
@@ -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,
|
||||
+1
-1
@@ -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(() => {
|
||||
+3
-3
@@ -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;
|
||||
@@ -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[] = [
|
||||
{
|
||||
+2
-2
@@ -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(() => {
|
||||
@@ -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();
|
||||
@@ -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;
|
||||
}
|
||||
+1
-1
@@ -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() {
|
||||
+2
-2
@@ -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() {
|
||||
+1
-1
@@ -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() {
|
||||
+3
-3
@@ -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();
|
||||
+1
-1
@@ -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(() => {
|
||||
+2
-2
@@ -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 {
|
||||
+1
-1
@@ -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 }) => {
|
||||
+2
-2
@@ -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 {
|
||||
@@ -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;
|
||||
}
|
||||
+2
-2
@@ -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({
|
||||
+1
-1
@@ -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(() => {
|
||||
+2
-2
@@ -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';
|
||||
+3
-3
@@ -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';
|
||||
+2
-2
@@ -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;
|
||||
+1
-1
@@ -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(() => {
|
||||
+3
-3
@@ -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();
|
||||
+2
-2
@@ -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 = [
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
}
|
||||
+2
-2
@@ -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`;
|
||||
|
||||
+1
-1
@@ -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;
|
||||
+2
-2
@@ -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',
|
||||
@@ -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;
|
||||
+1
-1
@@ -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;
|
||||
@@ -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[];
|
||||
}
|
||||
+1
-1
@@ -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;
|
||||
+2
-2
@@ -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[] = [
|
||||
{
|
||||
+2
-2
@@ -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(() => {
|
||||
@@ -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';
|
||||
@@ -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<UserRole, 'Owner'>;
|
||||
}
|
||||
|
||||
export interface InviteUserResponse {
|
||||
inviteLink: string;
|
||||
}
|
||||
|
||||
export interface ChangeRolePayload {
|
||||
newRole: UserRole;
|
||||
}
|
||||
+2
-2
@@ -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() {
|
||||
@@ -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<UserListItem[], Error>({
|
||||
@@ -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<T> = { 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
|
||||
|
||||
@@ -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';
|
||||
|
||||
+15
-15
@@ -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';
|
||||
|
||||
+12
-12
@@ -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<string, unknown>): { 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: () => (
|
||||
<RoleGuard allowedRoles={['Owner', 'Administrator']}>
|
||||
{lazyPage(() => import('@/pages/UsersPage'), 'UsersPage')()}
|
||||
{lazyPage(() => import('@/features/users/pages/UsersPage'), 'UsersPage')()}
|
||||
</RoleGuard>
|
||||
),
|
||||
});
|
||||
@@ -191,7 +191,7 @@ const cmsRoute = createRoute({
|
||||
component: () => (
|
||||
<RoleGuard allowedRoles={['Owner']}>
|
||||
<ModuleGuard requiredModule="Master">
|
||||
{lazyPage(() => import('@/pages/CmsPage'), 'CmsPage')()}
|
||||
{lazyPage(() => import('@/features/cms/pages/CmsPage'), 'CmsPage')()}
|
||||
</ModuleGuard>
|
||||
</RoleGuard>
|
||||
),
|
||||
@@ -202,7 +202,7 @@ const settingsRoute = createRoute({
|
||||
path: '/settings',
|
||||
component: () => (
|
||||
<RoleGuard allowedRoles={['Owner']}>
|
||||
{lazyPage(() => import('@/pages/SettingsPage'), 'SettingsPage')()}
|
||||
{lazyPage(() => import('@/features/settings/pages/SettingsPage'), 'SettingsPage')()}
|
||||
</RoleGuard>
|
||||
),
|
||||
});
|
||||
@@ -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({
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user