From b9d7a6ad69a4b5a524cc5dae3ff771d0d3a99224 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sat, 20 Jun 2026 21:57:04 +0300 Subject: [PATCH 1/3] refactor(frontend): complete FSD migration - import aliases, BrokerAccountLayout move, search api/ layer --- apps/frontend/src/app/layouts/AppLayout.tsx | 2 +- .../app/providers/SessionProvider.test.tsx | 4 +- .../src/app/providers/SessionProvider.tsx | 4 +- apps/frontend/src/app/routing/AppRoutes.tsx | 2 +- .../src/app/routing/ProtectedRoute.tsx | 2 +- .../bond/model/useBondCandles.test.tsx | 2 +- .../src/entities/search/api/searchApi.ts | 10 ++ apps/frontend/src/entities/search/index.ts | 1 + .../src/entities/search/model/useSearch.ts | 2 +- .../entities/session/api/sessionApi.test.ts | 2 +- .../stock/model/useStockCandles.test.tsx | 2 +- .../stock/model/useStockDividends.test.tsx | 2 +- .../ui/BrokerAccountOverviewPage.tsx | 8 +- .../broker-accounts/ui/BrokerAccountsPage.tsx | 6 +- .../ui/BrokerOperationsPage.tsx | 6 +- .../ui/BrokerPositionsPage.tsx | 4 +- .../src/pages/login/LoginPage.test.tsx | 4 +- .../src/pages/profile/ProfilePage.test.tsx | 4 +- .../src/pages/register/RegisterPage.test.tsx | 4 +- apps/frontend/src/shared/api/client.test.ts | 2 +- apps/frontend/src/shared/api/client.ts | 20 +--- apps/frontend/src/shared/api/index.ts | 9 +- apps/frontend/src/test/test-utils.tsx | 2 +- .../ui/BrokerAccountCard.tsx | 4 +- .../widgets/broker-account-layout/index.ts | 2 + .../ui/BrokerAccountLayout.tsx | 2 +- .../ui/BrokerAccountsSummary.tsx | 6 +- .../ui/BrokerAllocationChart.tsx | 5 +- .../ui/BrokerOperationsTable.tsx | 4 +- docs/features/frontend-fsd-final/plan.md | 108 ++++++++++++++++++ docs/features/frontend-fsd-final/spec.md | 35 ++++++ docs/features/frontend-fsd-final/tasks.md | 28 +++++ 32 files changed, 227 insertions(+), 71 deletions(-) create mode 100644 apps/frontend/src/entities/search/api/searchApi.ts create mode 100644 apps/frontend/src/widgets/broker-account-layout/index.ts rename apps/frontend/src/{entities/broker-account => widgets/broker-account-layout}/ui/BrokerAccountLayout.tsx (96%) create mode 100644 docs/features/frontend-fsd-final/plan.md create mode 100644 docs/features/frontend-fsd-final/spec.md create mode 100644 docs/features/frontend-fsd-final/tasks.md diff --git a/apps/frontend/src/app/layouts/AppLayout.tsx b/apps/frontend/src/app/layouts/AppLayout.tsx index 46f4408..fcdd1ff 100644 --- a/apps/frontend/src/app/layouts/AppLayout.tsx +++ b/apps/frontend/src/app/layouts/AppLayout.tsx @@ -1,6 +1,6 @@ import { Outlet, Link, useNavigate } from 'react-router-dom'; import { SearchBar } from '@/widgets/search-bar'; -import { useSession } from '@/entities/session/model/useSession'; +import { useSession } from '@/entities/session'; export function AppLayout() { const { isAuthenticated, user, logout } = useSession(); diff --git a/apps/frontend/src/app/providers/SessionProvider.test.tsx b/apps/frontend/src/app/providers/SessionProvider.test.tsx index f7750ce..3f13cf1 100644 --- a/apps/frontend/src/app/providers/SessionProvider.test.tsx +++ b/apps/frontend/src/app/providers/SessionProvider.test.tsx @@ -3,8 +3,8 @@ import { useContext } from 'react'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; -import { server } from '../../test/server'; -import { SessionContext } from '@/entities/session/model/sessionContext'; +import { server } from '@/test/server'; +import { SessionContext } from '@/entities/session'; import { SessionProvider } from './SessionProvider'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; diff --git a/apps/frontend/src/app/providers/SessionProvider.tsx b/apps/frontend/src/app/providers/SessionProvider.tsx index 871930d..39a305b 100644 --- a/apps/frontend/src/app/providers/SessionProvider.tsx +++ b/apps/frontend/src/app/providers/SessionProvider.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useCallback, type ReactNode } from 'react'; -import * as sessionApi from '@/entities/session/api/sessionApi'; -import { SessionContext, type SessionContextValue } from '@/entities/session/model/sessionContext'; +import * as sessionApi from '@/entities/session'; +import { SessionContext, type SessionContextValue } from '@/entities/session'; import { setOnUnauthorized } from '@/shared/api/client'; import type { UserResponse } from '@/shared/api/responses'; diff --git a/apps/frontend/src/app/routing/AppRoutes.tsx b/apps/frontend/src/app/routing/AppRoutes.tsx index 3fdd335..71efb7d 100644 --- a/apps/frontend/src/app/routing/AppRoutes.tsx +++ b/apps/frontend/src/app/routing/AppRoutes.tsx @@ -10,7 +10,7 @@ import { ProfilePage } from '@/pages/profile'; import { PortfoliosListPage, PortfolioDetailPage } from '@/pages/portfolios'; import { ScreenerPage } from '@/pages/screener'; import { BrokerAccountsPage } from '@/pages/broker-accounts'; -import { BrokerAccountLayout } from '@/entities/broker-account/ui/BrokerAccountLayout'; +import { BrokerAccountLayout } from '@/widgets/broker-account-layout'; import { BrokerAccountOverviewPage } from '@/pages/broker-account'; import { BrokerPositionsPage } from '@/pages/broker-positions'; import { BrokerOperationsPage } from '@/pages/broker-operations'; diff --git a/apps/frontend/src/app/routing/ProtectedRoute.tsx b/apps/frontend/src/app/routing/ProtectedRoute.tsx index d5fccb5..5232231 100644 --- a/apps/frontend/src/app/routing/ProtectedRoute.tsx +++ b/apps/frontend/src/app/routing/ProtectedRoute.tsx @@ -1,5 +1,5 @@ import { Navigate, useLocation } from 'react-router-dom'; -import { useSession } from '@/entities/session/model/useSession'; +import { useSession } from '@/entities/session'; import type { ReactNode } from 'react'; export function ProtectedRoute({ children }: { children: ReactNode }) { diff --git a/apps/frontend/src/entities/bond/model/useBondCandles.test.tsx b/apps/frontend/src/entities/bond/model/useBondCandles.test.tsx index 08a159d..66807df 100644 --- a/apps/frontend/src/entities/bond/model/useBondCandles.test.tsx +++ b/apps/frontend/src/entities/bond/model/useBondCandles.test.tsx @@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest'; import { renderHook, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { http, HttpResponse } from 'msw'; -import { server } from '../../../test/server'; +import { server } from '@/test/server'; import { useBondCandles } from './useBondCandles'; import { type ReactNode } from 'react'; diff --git a/apps/frontend/src/entities/search/api/searchApi.ts b/apps/frontend/src/entities/search/api/searchApi.ts new file mode 100644 index 0000000..5b7388e --- /dev/null +++ b/apps/frontend/src/entities/search/api/searchApi.ts @@ -0,0 +1,10 @@ +import { request } from '@/shared/api/client'; +import type { SearchResultItem } from '@/shared/api/responses'; + +export function searchSecurities(q: string, type: 'all' | 'share' | 'bond' = 'all', limit = 20) { + return request('/api/v1/securities/search', { + q, + type, + limit: String(limit), + }); +} diff --git a/apps/frontend/src/entities/search/index.ts b/apps/frontend/src/entities/search/index.ts index 06cdb77..f152f29 100644 --- a/apps/frontend/src/entities/search/index.ts +++ b/apps/frontend/src/entities/search/index.ts @@ -1 +1,2 @@ export { useSearch } from './model/useSearch'; +export { searchSecurities } from './api/searchApi'; diff --git a/apps/frontend/src/entities/search/model/useSearch.ts b/apps/frontend/src/entities/search/model/useSearch.ts index fb5de32..eafdc21 100644 --- a/apps/frontend/src/entities/search/model/useSearch.ts +++ b/apps/frontend/src/entities/search/model/useSearch.ts @@ -1,5 +1,5 @@ import { useQuery } from '@tanstack/react-query'; -import { searchSecurities } from '@/shared/api/client'; +import { searchSecurities } from '../api/searchApi'; import type { SearchResultItem } from '@/shared/api/responses'; export function useSearch(query: string) { diff --git a/apps/frontend/src/entities/session/api/sessionApi.test.ts b/apps/frontend/src/entities/session/api/sessionApi.test.ts index af29bfd..af9f582 100644 --- a/apps/frontend/src/entities/session/api/sessionApi.test.ts +++ b/apps/frontend/src/entities/session/api/sessionApi.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeEach } from 'vitest'; import { http, HttpResponse } from 'msw'; -import { server } from '../../../test/server'; +import { server } from '@/test/server'; import { setAccessToken, getAccessToken } from '@/shared/api/client'; import { login, register, refresh, logout, getMe, updateProfile } from './sessionApi'; diff --git a/apps/frontend/src/entities/stock/model/useStockCandles.test.tsx b/apps/frontend/src/entities/stock/model/useStockCandles.test.tsx index be437f4..d106040 100644 --- a/apps/frontend/src/entities/stock/model/useStockCandles.test.tsx +++ b/apps/frontend/src/entities/stock/model/useStockCandles.test.tsx @@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest'; import { renderHook, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { http, HttpResponse } from 'msw'; -import { server } from '../../../test/server'; +import { server } from '@/test/server'; import { useStockCandles } from './useStockCandles'; import { type ReactNode } from 'react'; diff --git a/apps/frontend/src/entities/stock/model/useStockDividends.test.tsx b/apps/frontend/src/entities/stock/model/useStockDividends.test.tsx index 9e899ae..5ca5b6c 100644 --- a/apps/frontend/src/entities/stock/model/useStockDividends.test.tsx +++ b/apps/frontend/src/entities/stock/model/useStockDividends.test.tsx @@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest'; import { renderHook, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { http, HttpResponse } from 'msw'; -import { server } from '../../../test/server'; +import { server } from '@/test/server'; import { useStockDividends } from './useStockDividends'; import { type ReactNode } from 'react'; diff --git a/apps/frontend/src/pages/broker-account/ui/BrokerAccountOverviewPage.tsx b/apps/frontend/src/pages/broker-account/ui/BrokerAccountOverviewPage.tsx index 9c965af..3656712 100644 --- a/apps/frontend/src/pages/broker-account/ui/BrokerAccountOverviewPage.tsx +++ b/apps/frontend/src/pages/broker-account/ui/BrokerAccountOverviewPage.tsx @@ -1,10 +1,10 @@ import { Link } from 'react-router-dom'; import type { BrokerMoney, BrokerPortfolio } from '@/shared/api/responses'; import { SkeletonBlock } from '@/shared/ui/SkeletonBlock'; -import { useBrokerOperations } from '../../../entities/broker-operation'; -import { useBrokerAccountContext } from '../../../entities/broker-account/ui/BrokerAccountLayout'; -import { BrokerAllocationChart } from '../../../widgets/broker-allocation-chart'; -import { BrokerOperationsTable } from '../../../widgets/broker-operations-table'; +import { useBrokerOperations } from '@/entities/broker-operation'; +import { useBrokerAccountContext } from '@/widgets/broker-account-layout'; +import { BrokerAllocationChart } from '@/widgets/broker-allocation-chart'; +import { BrokerOperationsTable } from '@/widgets/broker-operations-table'; function formatMoney(value: BrokerMoney | null | undefined) { if (!value) return '—'; diff --git a/apps/frontend/src/pages/broker-accounts/ui/BrokerAccountsPage.tsx b/apps/frontend/src/pages/broker-accounts/ui/BrokerAccountsPage.tsx index aaa0e05..ba3bff0 100644 --- a/apps/frontend/src/pages/broker-accounts/ui/BrokerAccountsPage.tsx +++ b/apps/frontend/src/pages/broker-accounts/ui/BrokerAccountsPage.tsx @@ -2,9 +2,9 @@ import { aggregateBrokerAccounts, useBrokerAccounts, useBrokerAccountPortfolios, -} from '../../../entities/broker-account'; -import { BrokerAccountCard } from '../../../widgets/broker-account-card'; -import { BrokerAccountsSummary } from '../../../widgets/broker-accounts-summary'; +} from '@/entities/broker-account'; +import { BrokerAccountCard } from '@/widgets/broker-account-card'; +import { BrokerAccountsSummary } from '@/widgets/broker-accounts-summary'; function BrokerAccountsPageSkeleton() { return ( diff --git a/apps/frontend/src/pages/broker-operations/ui/BrokerOperationsPage.tsx b/apps/frontend/src/pages/broker-operations/ui/BrokerOperationsPage.tsx index 892eff7..a71ade0 100644 --- a/apps/frontend/src/pages/broker-operations/ui/BrokerOperationsPage.tsx +++ b/apps/frontend/src/pages/broker-operations/ui/BrokerOperationsPage.tsx @@ -4,9 +4,9 @@ import { BROKER_OPERATION_TYPE_OPTIONS, isBrokerOperationType, useBrokerOperations, -} from '../../../entities/broker-operation'; -import { useBrokerAccountContext } from '../../../entities/broker-account/ui/BrokerAccountLayout'; -import { BrokerOperationsTable } from '../../../widgets/broker-operations-table'; +} from '@/entities/broker-operation'; +import { useBrokerAccountContext } from '@/widgets/broker-account-layout'; +import { BrokerOperationsTable } from '@/widgets/broker-operations-table'; export function BrokerOperationsPage() { const { accountId } = useBrokerAccountContext(); diff --git a/apps/frontend/src/pages/broker-positions/ui/BrokerPositionsPage.tsx b/apps/frontend/src/pages/broker-positions/ui/BrokerPositionsPage.tsx index 63f6cd1..041d298 100644 --- a/apps/frontend/src/pages/broker-positions/ui/BrokerPositionsPage.tsx +++ b/apps/frontend/src/pages/broker-positions/ui/BrokerPositionsPage.tsx @@ -6,8 +6,8 @@ import type { BrokerPositionsPage as BrokerPositionsPageData, } from '@/shared/api/responses'; import { TableSkeleton } from '@/shared/ui/TableSkeleton'; -import { getBrokerInstrumentPath, useBrokerPositions } from '../../../entities/broker-position'; -import { useBrokerAccountContext } from '../../../entities/broker-account/ui/BrokerAccountLayout'; +import { getBrokerInstrumentPath, useBrokerPositions } from '@/entities/broker-position'; +import { useBrokerAccountContext } from '@/widgets/broker-account-layout'; const tableStyle = { width: '100%', diff --git a/apps/frontend/src/pages/login/LoginPage.test.tsx b/apps/frontend/src/pages/login/LoginPage.test.tsx index 4932b74..ec78ee3 100644 --- a/apps/frontend/src/pages/login/LoginPage.test.tsx +++ b/apps/frontend/src/pages/login/LoginPage.test.tsx @@ -3,9 +3,9 @@ import { screen } from '@testing-library/react'; import { Routes, Route } from 'react-router-dom'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; -import { server } from '../../test/server'; +import { server } from '@/test/server'; import { LoginPage } from './ui/LoginPage'; -import { renderWithProviders } from '../../test/test-utils'; +import { renderWithProviders } from '@/test/test-utils'; const API = '/api/v1'; diff --git a/apps/frontend/src/pages/profile/ProfilePage.test.tsx b/apps/frontend/src/pages/profile/ProfilePage.test.tsx index e4bc956..321d6b6 100644 --- a/apps/frontend/src/pages/profile/ProfilePage.test.tsx +++ b/apps/frontend/src/pages/profile/ProfilePage.test.tsx @@ -2,9 +2,9 @@ import { describe, it, expect } from 'vitest'; import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; -import { server } from '../../test/server'; +import { server } from '@/test/server'; import { ProfilePage } from './ui/ProfilePage'; -import { renderWithProviders } from '../../test/test-utils'; +import { renderWithProviders } from '@/test/test-utils'; const API = '/api/v1'; diff --git a/apps/frontend/src/pages/register/RegisterPage.test.tsx b/apps/frontend/src/pages/register/RegisterPage.test.tsx index 832cb29..71f0eab 100644 --- a/apps/frontend/src/pages/register/RegisterPage.test.tsx +++ b/apps/frontend/src/pages/register/RegisterPage.test.tsx @@ -3,9 +3,9 @@ import { screen } from '@testing-library/react'; import { Routes, Route } from 'react-router-dom'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; -import { server } from '../../test/server'; +import { server } from '@/test/server'; import { RegisterPage } from './ui/RegisterPage'; -import { renderWithProviders } from '../../test/test-utils'; +import { renderWithProviders } from '@/test/test-utils'; const API = '/api/v1'; diff --git a/apps/frontend/src/shared/api/client.test.ts b/apps/frontend/src/shared/api/client.test.ts index 1272a13..f1a6fc3 100644 --- a/apps/frontend/src/shared/api/client.test.ts +++ b/apps/frontend/src/shared/api/client.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeEach } from 'vitest'; import { http, HttpResponse } from 'msw'; -import { server } from '../../test/server'; +import { server } from '@/test/server'; import { request, setAccessToken, getAccessToken, setOnUnauthorized } from './client'; const API = '/api/v1'; diff --git a/apps/frontend/src/shared/api/client.ts b/apps/frontend/src/shared/api/client.ts index d5e60ba..38ec072 100644 --- a/apps/frontend/src/shared/api/client.ts +++ b/apps/frontend/src/shared/api/client.ts @@ -1,10 +1,4 @@ -import type { - ApiEnvelope, - ApiResponseMeta, - AuthResponse, - SearchResultItem, - HealthResponse, -} from './responses'; +import type { ApiEnvelope, ApiResponseMeta, AuthResponse, HealthResponse } from './responses'; const BASE = ''; @@ -130,15 +124,3 @@ export async function request( export function getHealth(): Promise<{ data: HealthResponse; meta: ApiResponseMeta }> { return request('/api/v1/health'); } - -export function searchSecurities( - q: string, - type: 'all' | 'share' | 'bond' = 'all', - limit = 20, -): Promise<{ data: SearchResultItem[]; meta: ApiResponseMeta }> { - return request('/api/v1/securities/search', { - q, - type, - limit: String(limit), - }); -} diff --git a/apps/frontend/src/shared/api/index.ts b/apps/frontend/src/shared/api/index.ts index 5d16891..f86b529 100644 --- a/apps/frontend/src/shared/api/index.ts +++ b/apps/frontend/src/shared/api/index.ts @@ -1,11 +1,4 @@ -export { - request, - setAccessToken, - getAccessToken, - setOnUnauthorized, - getHealth, - searchSecurities, -} from './client'; +export { request, setAccessToken, getAccessToken, setOnUnauthorized, getHealth } from './client'; export type { ApiResponseMeta, ApiEnvelope, diff --git a/apps/frontend/src/test/test-utils.tsx b/apps/frontend/src/test/test-utils.tsx index 971b7da..7a765c8 100644 --- a/apps/frontend/src/test/test-utils.tsx +++ b/apps/frontend/src/test/test-utils.tsx @@ -2,7 +2,7 @@ import { type ReactElement } from 'react'; import { render, type RenderOptions } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { MemoryRouter } from 'react-router-dom'; -import { SessionProvider } from '../app/providers/SessionProvider'; +import { SessionProvider } from '@/app/providers/SessionProvider'; interface CustomRenderOptions extends Omit { queryClient?: QueryClient; diff --git a/apps/frontend/src/widgets/broker-account-card/ui/BrokerAccountCard.tsx b/apps/frontend/src/widgets/broker-account-card/ui/BrokerAccountCard.tsx index c45a3f3..cf4f171 100644 --- a/apps/frontend/src/widgets/broker-account-card/ui/BrokerAccountCard.tsx +++ b/apps/frontend/src/widgets/broker-account-card/ui/BrokerAccountCard.tsx @@ -1,8 +1,8 @@ import { Link } from 'react-router-dom'; import { SkeletonBlock } from '@/shared/ui/SkeletonBlock'; import type { BrokerAccount, BrokerMoney, BrokerPortfolio } from '@/shared/api/responses'; -import { buildBrokerAllocation } from '../../../entities/broker-position'; -import { BrokerAllocationBar } from '../../../widgets/broker-allocation-chart'; +import { buildBrokerAllocation } from '@/entities/broker-position'; +import { BrokerAllocationBar } from '@/widgets/broker-allocation-chart'; function formatBrokerCurrencyValue(currency: string, value: number): string { return new Intl.NumberFormat('ru-RU', { diff --git a/apps/frontend/src/widgets/broker-account-layout/index.ts b/apps/frontend/src/widgets/broker-account-layout/index.ts new file mode 100644 index 0000000..d78b2fc --- /dev/null +++ b/apps/frontend/src/widgets/broker-account-layout/index.ts @@ -0,0 +1,2 @@ +export { BrokerAccountLayout, useBrokerAccountContext } from './ui/BrokerAccountLayout'; +export type { BrokerAccountContext } from './ui/BrokerAccountLayout'; diff --git a/apps/frontend/src/entities/broker-account/ui/BrokerAccountLayout.tsx b/apps/frontend/src/widgets/broker-account-layout/ui/BrokerAccountLayout.tsx similarity index 96% rename from apps/frontend/src/entities/broker-account/ui/BrokerAccountLayout.tsx rename to apps/frontend/src/widgets/broker-account-layout/ui/BrokerAccountLayout.tsx index 6d784c6..5d34f26 100644 --- a/apps/frontend/src/entities/broker-account/ui/BrokerAccountLayout.tsx +++ b/apps/frontend/src/widgets/broker-account-layout/ui/BrokerAccountLayout.tsx @@ -1,5 +1,5 @@ import { NavLink, Outlet, useOutletContext, useParams } from 'react-router-dom'; -import { useBrokerPortfolio } from '../model/useBrokerPortfolio'; +import { useBrokerPortfolio } from '@/entities/broker-account'; export type BrokerAccountContext = { accountId: string; diff --git a/apps/frontend/src/widgets/broker-accounts-summary/ui/BrokerAccountsSummary.tsx b/apps/frontend/src/widgets/broker-accounts-summary/ui/BrokerAccountsSummary.tsx index e5e4b7a..91b61b3 100644 --- a/apps/frontend/src/widgets/broker-accounts-summary/ui/BrokerAccountsSummary.tsx +++ b/apps/frontend/src/widgets/broker-accounts-summary/ui/BrokerAccountsSummary.tsx @@ -1,7 +1,7 @@ import { SkeletonBlock } from '@/shared/ui/SkeletonBlock'; -import type { BrokerAccountsAggregate } from '../../../entities/broker-account/model/brokerAccountsOverview'; -import { buildBrokerAllocation } from '../../../entities/broker-position'; -import { BrokerAllocationBar } from '../../../widgets/broker-allocation-chart'; +import type { BrokerAccountsAggregate } from '@/entities/broker-account/model/brokerAccountsOverview'; +import { buildBrokerAllocation } from '@/entities/broker-position'; +import { BrokerAllocationBar } from '@/widgets/broker-allocation-chart'; function formatBrokerCurrencyValue(currency: string, value: number): string { return new Intl.NumberFormat('ru-RU', { diff --git a/apps/frontend/src/widgets/broker-allocation-chart/ui/BrokerAllocationChart.tsx b/apps/frontend/src/widgets/broker-allocation-chart/ui/BrokerAllocationChart.tsx index 683bf22..786f948 100644 --- a/apps/frontend/src/widgets/broker-allocation-chart/ui/BrokerAllocationChart.tsx +++ b/apps/frontend/src/widgets/broker-allocation-chart/ui/BrokerAllocationChart.tsx @@ -1,8 +1,5 @@ import type { BrokerPortfolio } from '@/shared/api/responses'; -import { - buildBrokerAllocation, - type BrokerAllocationItem, -} from '../../../entities/broker-position'; +import { buildBrokerAllocation, type BrokerAllocationItem } from '@/entities/broker-position'; const RADIUS = 44; const CIRCUMFERENCE = 2 * Math.PI * RADIUS; diff --git a/apps/frontend/src/widgets/broker-operations-table/ui/BrokerOperationsTable.tsx b/apps/frontend/src/widgets/broker-operations-table/ui/BrokerOperationsTable.tsx index 3169d76..8b60681 100644 --- a/apps/frontend/src/widgets/broker-operations-table/ui/BrokerOperationsTable.tsx +++ b/apps/frontend/src/widgets/broker-operations-table/ui/BrokerOperationsTable.tsx @@ -6,8 +6,8 @@ import { getBrokerOperationImpact, getBrokerOperationTypeLabel, type BrokerOperationImpact, -} from '../../../entities/broker-operation'; -import { getBrokerInstrumentPath } from '../../../entities/broker-position'; +} from '@/entities/broker-operation'; +import { getBrokerInstrumentPath } from '@/entities/broker-position'; const tableStyle = { width: '100%', diff --git a/docs/features/frontend-fsd-final/plan.md b/docs/features/frontend-fsd-final/plan.md new file mode 100644 index 0000000..a17a0e7 --- /dev/null +++ b/docs/features/frontend-fsd-final/plan.md @@ -0,0 +1,108 @@ +# FSD Final Cleanup Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement task-by-task. + +**Goal:** Complete FSD migration by fixing all remaining compliance gaps in apps/frontend/src/ + +**Architecture:** Five independent phases: (1) fix `../../../` → `@/` in 8 broker files, (2) move `BrokerAccountLayout` from entities to widgets, (3) add `api/` to entities/search, (4) fix app layer deep imports to use barrel, (5) fix test relative imports to use `@/`. Each phase is safe, mechanical, and independently verifiable. + +**Tech Stack:** TypeScript, React, Feature-Sliced Design, Vitest + +--- + +## File Structure Changes + +### Create +- `apps/frontend/src/widgets/broker-account-layout/index.ts` +- `apps/frontend/src/widgets/broker-account-layout/ui/BrokerAccountLayout.tsx` +- `apps/frontend/src/entities/search/api/searchApi.ts` + +### Delete +- `apps/frontend/src/entities/broker-account/ui/BrokerAccountLayout.tsx` +- `apps/frontend/src/entities/broker-account/ui/` (if empty) + +### Modify (20+ files) +See per-task sections below. + +--- + +## Phase 1: Fix import aliases + +### Task 1: Fix `../../../` → `@/` in 8 broker files + +**Files to modify:** +- `apps/frontend/src/pages/broker-accounts/ui/BrokerAccountsPage.tsx` — 3 relative imports +- `apps/frontend/src/pages/broker-positions/ui/BrokerPositionsPage.tsx` — 2 relative imports +- `apps/frontend/src/pages/broker-operations/ui/BrokerOperationsPage.tsx` — 3 relative imports +- `apps/frontend/src/pages/broker-account/ui/BrokerAccountOverviewPage.tsx` — 4 relative imports +- `apps/frontend/src/widgets/broker-allocation-chart/ui/BrokerAllocationChart.tsx` — 1 relative import +- `apps/frontend/src/widgets/broker-operations-table/ui/BrokerOperationsTable.tsx` — 2 relative imports +- `apps/frontend/src/widgets/broker-account-card/ui/BrokerAccountCard.tsx` — 2 relative imports +- `apps/frontend/src/widgets/broker-accounts-summary/ui/BrokerAccountsSummary.tsx` — 3 relative imports + +Each replacement follows the same pattern: `../../../entities/...` → `@/entities/...` and `../../../widgets/...` → `@/widgets/...`. + +For pages at depth 2 (`../../entities/`) — same prefix. Only files at depth 3 (`../../../`) exist in this set. + +Note: 3 pages import `useBrokerAccountContext` from `../../../entities/broker-account/ui/BrokerAccountLayout` — these will be updated again in Phase 2 after the move. + +## Phase 2: Move BrokerAccountLayout to widgets + +### Task 2: Create widgets/broker-account-layout + +- `widgets/broker-account-layout/index.ts` — re-exports `BrokerAccountLayout` and `useBrokerAccountContext` +- `widgets/broker-account-layout/ui/BrokerAccountLayout.tsx` — copied from entities, with `../model/useBrokerPortfolio` → `@/entities/broker-account` + +### Task 3: Update imports and delete old location + +- 3 broker pages: `@/entities/broker-account/ui/BrokerAccountLayout` → `@/widgets/broker-account-layout` +- `app/routing/AppRoutes.tsx`: same update +- Delete `entities/broker-account/ui/BrokerAccountLayout.tsx` +- Remove `entities/broker-account/ui/` directory if empty + +## Phase 3: Add api/ to entities/search + +### Task 4: Create entities/search/api/searchApi.ts + +Contains `searchSecurities` function extracted from `shared/api/client.ts`. Imports `request` from shared. + +### Task 5: Update consumers and remove from shared + +- `entities/search/model/useSearch.ts`: `@/shared/api/client` → `../api/searchApi` +- `entities/search/index.ts`: add re-export of `searchSecurities` +- `shared/api/client.ts`: remove `searchSecurities` function +- `shared/api/index.ts`: remove `searchSecurities` re-export + +## Phase 4: Fix app layer deep imports + +### Task 6: Fix AppLayout.tsx, SessionProvider.tsx, ProtectedRoute.tsx + +- `app/layouts/AppLayout.tsx`: `@/entities/session/model/useSession` → `@/entities/session` +- `app/providers/SessionProvider.tsx`: both deep imports → `@/entities/session` +- `app/routing/ProtectedRoute.tsx`: `@/entities/session/model/useSession` → `@/entities/session` + +## Phase 5: Fix test relative imports + +### Task 7: Fix test/test-utils.tsx relative import + +- `test/test-utils.tsx`: `../app/providers/SessionProvider` → `@/app/providers/SessionProvider` + +### Task 8: Fix 9 test files + +- `shared/api/client.test.ts`: `../../test/server` → `@/test/server` +- `entities/session/api/sessionApi.test.ts`: `../../../test/server` → `@/test/server` +- `entities/stock/model/useStockDividends.test.tsx`: `../../../test/server` → `@/test/server` +- `entities/stock/model/useStockCandles.test.tsx`: `../../../test/server` → `@/test/server` +- `entities/bond/model/useBondCandles.test.tsx`: `../../../test/server` → `@/test/server` +- `app/providers/SessionProvider.test.tsx`: `../../test/server` → `@/test/server` +- `pages/login/LoginPage.test.tsx`: `../../test/server` → `@/test/server`, `../../test/test-utils` → `@/test/test-utils` +- `pages/register/RegisterPage.test.tsx`: same +- `pages/profile/ProfilePage.test.tsx`: same + +## Verification + +### Task 9: Run tests, lint, build + +- `npm run lint -w apps/frontend` +- `npm run test -w apps/frontend` +- `npm run build -w apps/frontend` diff --git a/docs/features/frontend-fsd-final/spec.md b/docs/features/frontend-fsd-final/spec.md new file mode 100644 index 0000000..7ef4e28 --- /dev/null +++ b/docs/features/frontend-fsd-final/spec.md @@ -0,0 +1,35 @@ +# FSD Final Cleanup + +## Goal + +Complete the Feature-Sliced Design (FSD) migration of the frontend codebase by eliminating all remaining architecture compliance gaps — relative cross-layer imports, misplaced components, missing API layers, and deep imports into entity internals. + +## Requirements + +### R1: All imports use `@/` path aliases +No `../../../` or `../../` relative imports across the entire `src/` tree. Every import must use the `@/` alias pointing to `src/`. + +### R2: BrokerAccountLayout lives in widgets layer +The `BrokerAccountLayout` component (layout with routing + `useOutletContext`) currently lives in `entities/broker-account/ui/`. It must be moved to `widgets/broker-account-layout/` since it is a page layout, not a business entity. + +### R3: entities/search has an `api/` layer +The `searchSecurities` function — a domain API call — currently lives in `shared/api/client.ts`. It must be moved to `entities/search/api/searchApi.ts` so that each entity owns its domain calls. + +### R4: App layer uses barrel imports +Files in `app/` must import from entity barrel files (`@/entities/session`) rather than deep-importing into `model/` or `api/` subdirectories. + +### R5: Tests use `@/` path aliases +All test files must import `test/server` and `test/test-utils` via `@/` prefix instead of relative paths. + +## Constraints + +- Only modify imports and restructure entities/search and BrokerAccountLayout. Do not change business logic. +- Share `request()` from shared/api — `entities/search/api/searchApi.ts` imports `request` from shared. +- `entities/broker-account/index.ts` barrel exports remain unchanged. + +## Out of Scope + +- Moving `styles.css` or `main.tsx` into `app/` +- Type deduplication (`responses.ts` vs `types.ts`) +- Moving `src/test/` to `shared/lib/tests/` +- Refactoring `entities/broker-account/model/brokerAccountsOverview` deep imports diff --git a/docs/features/frontend-fsd-final/tasks.md b/docs/features/frontend-fsd-final/tasks.md new file mode 100644 index 0000000..d81606d --- /dev/null +++ b/docs/features/frontend-fsd-final/tasks.md @@ -0,0 +1,28 @@ +# Tasks: FSD Final Cleanup + +## Phase 1: Fix import aliases + +- [x] **Task 1:** Fix `../../../` → `@/` in 8 broker files (pages + widgets) + +## Phase 2: Move BrokerAccountLayout to widgets + +- [x] **Task 2:** Create `widgets/broker-account-layout/index.ts` and `ui/BrokerAccountLayout.tsx` +- [x] **Task 3:** Update all imports referencing old path, delete `entities/broker-account/ui/BrokerAccountLayout.tsx` + +## Phase 3: Add api/ to entities/search + +- [x] **Task 4:** Create `entities/search/api/searchApi.ts` with `searchSecurities` +- [x] **Task 5:** Update consumers, remove `searchSecurities` from shared + +## Phase 4: Fix app layer deep imports + +- [x] **Task 6:** Fix `AppLayout.tsx`, `SessionProvider.tsx`, `ProtectedRoute.tsx` to use `@/entities/session` barrel + +## Phase 5: Fix test relative imports + +- [x] **Task 7:** Fix `test/test-utils.tsx` relative import +- [x] **Task 8:** Fix 9 test files with relative `test/` imports + +## Verification + +- [x] **Task 9:** Run tests, lint, build — all pass -- 2.47.2 From 2fd2f0611b5a9bf4aeee930de9afa3eadc7d75b8 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sat, 20 Jun 2026 22:07:25 +0300 Subject: [PATCH 2/3] refactor(frontend): move BrokerAllocationBar to shared/ui, fix cross-entity imports and missing barrel exports --- .../src/entities/broker-account/index.ts | 5 +- .../broker-position/model/brokerDisplay.ts | 4 +- .../shared/ui/broker-allocation-bar/index.ts | 1 + .../ui/BrokerAllocationBar.tsx | 49 +++++++++ .../ui/BrokerAccountCard.tsx | 2 +- .../ui/BrokerAccountsSummary.tsx | 4 +- .../widgets/broker-allocation-chart/index.ts | 2 +- .../ui/BrokerAllocationChart.tsx | 44 +-------- docs/features/frontend-fsd-final/plan.md | 99 ++++--------------- docs/features/frontend-fsd-final/spec.md | 19 +++- docs/features/frontend-fsd-final/tasks.md | 16 ++- 11 files changed, 109 insertions(+), 136 deletions(-) create mode 100644 apps/frontend/src/shared/ui/broker-allocation-bar/index.ts create mode 100644 apps/frontend/src/shared/ui/broker-allocation-bar/ui/BrokerAllocationBar.tsx diff --git a/apps/frontend/src/entities/broker-account/index.ts b/apps/frontend/src/entities/broker-account/index.ts index e0ef9ab..e6e7c28 100644 --- a/apps/frontend/src/entities/broker-account/index.ts +++ b/apps/frontend/src/entities/broker-account/index.ts @@ -1,7 +1,10 @@ export { useBrokerAccounts } from './model/useBrokerAccounts'; export { useBrokerAccountPortfolios } from './model/useBrokerAccountPortfolios'; export { useBrokerPortfolio } from './model/useBrokerPortfolio'; -export { aggregateBrokerAccounts } from './model/brokerAccountsOverview'; +export { + aggregateBrokerAccounts, + type BrokerAccountsAggregate, +} from './model/brokerAccountsOverview'; export { getBrokerAccounts, getBrokerPortfolio, diff --git a/apps/frontend/src/entities/broker-position/model/brokerDisplay.ts b/apps/frontend/src/entities/broker-position/model/brokerDisplay.ts index d503d21..904dfa2 100644 --- a/apps/frontend/src/entities/broker-position/model/brokerDisplay.ts +++ b/apps/frontend/src/entities/broker-position/model/brokerDisplay.ts @@ -4,8 +4,8 @@ export { getBrokerOperationImpact, getBrokerOperationTypeLabel, isBrokerOperationType, -} from '../../broker-operation/model/operationFilters'; -export type { BrokerOperationImpact } from '../../broker-operation/model/operationFilters'; +} from '@/entities/broker-operation'; +export type { BrokerOperationImpact } from '@/entities/broker-operation'; export type BrokerPositionGroup = 'shares' | 'bonds' | 'other'; diff --git a/apps/frontend/src/shared/ui/broker-allocation-bar/index.ts b/apps/frontend/src/shared/ui/broker-allocation-bar/index.ts new file mode 100644 index 0000000..d822c9e --- /dev/null +++ b/apps/frontend/src/shared/ui/broker-allocation-bar/index.ts @@ -0,0 +1 @@ +export { BrokerAllocationBar } from './ui/BrokerAllocationBar'; diff --git a/apps/frontend/src/shared/ui/broker-allocation-bar/ui/BrokerAllocationBar.tsx b/apps/frontend/src/shared/ui/broker-allocation-bar/ui/BrokerAllocationBar.tsx new file mode 100644 index 0000000..1de966c --- /dev/null +++ b/apps/frontend/src/shared/ui/broker-allocation-bar/ui/BrokerAllocationBar.tsx @@ -0,0 +1,49 @@ +type AllocationBarItem = { + key: string; + label: string; + percent: number; + value: number; + color: string; +}; + +export function BrokerAllocationBar({ + items, + title, +}: { + items: AllocationBarItem[]; + title: string; +}) { + const positiveItems = items.filter((item) => item.value > 0); + + if (positiveItems.length === 0) { + return

Нет данных для распределения

; + } + + return ( +
+
+ {positiveItems.map((item) => ( +
+
    + {positiveItems.map((item) => ( +
  • +
  • + ))} +
+
+ ); +} diff --git a/apps/frontend/src/widgets/broker-account-card/ui/BrokerAccountCard.tsx b/apps/frontend/src/widgets/broker-account-card/ui/BrokerAccountCard.tsx index cf4f171..cee8bed 100644 --- a/apps/frontend/src/widgets/broker-account-card/ui/BrokerAccountCard.tsx +++ b/apps/frontend/src/widgets/broker-account-card/ui/BrokerAccountCard.tsx @@ -2,7 +2,7 @@ import { Link } from 'react-router-dom'; import { SkeletonBlock } from '@/shared/ui/SkeletonBlock'; import type { BrokerAccount, BrokerMoney, BrokerPortfolio } from '@/shared/api/responses'; import { buildBrokerAllocation } from '@/entities/broker-position'; -import { BrokerAllocationBar } from '@/widgets/broker-allocation-chart'; +import { BrokerAllocationBar } from '@/shared/ui/broker-allocation-bar'; function formatBrokerCurrencyValue(currency: string, value: number): string { return new Intl.NumberFormat('ru-RU', { diff --git a/apps/frontend/src/widgets/broker-accounts-summary/ui/BrokerAccountsSummary.tsx b/apps/frontend/src/widgets/broker-accounts-summary/ui/BrokerAccountsSummary.tsx index 91b61b3..e906426 100644 --- a/apps/frontend/src/widgets/broker-accounts-summary/ui/BrokerAccountsSummary.tsx +++ b/apps/frontend/src/widgets/broker-accounts-summary/ui/BrokerAccountsSummary.tsx @@ -1,7 +1,7 @@ import { SkeletonBlock } from '@/shared/ui/SkeletonBlock'; -import type { BrokerAccountsAggregate } from '@/entities/broker-account/model/brokerAccountsOverview'; +import type { BrokerAccountsAggregate } from '@/entities/broker-account'; import { buildBrokerAllocation } from '@/entities/broker-position'; -import { BrokerAllocationBar } from '@/widgets/broker-allocation-chart'; +import { BrokerAllocationBar } from '@/shared/ui/broker-allocation-bar'; function formatBrokerCurrencyValue(currency: string, value: number): string { return new Intl.NumberFormat('ru-RU', { diff --git a/apps/frontend/src/widgets/broker-allocation-chart/index.ts b/apps/frontend/src/widgets/broker-allocation-chart/index.ts index b99acd8..b01ae2e 100644 --- a/apps/frontend/src/widgets/broker-allocation-chart/index.ts +++ b/apps/frontend/src/widgets/broker-allocation-chart/index.ts @@ -1 +1 @@ -export { BrokerAllocationBar, BrokerAllocationChart } from './ui/BrokerAllocationChart'; +export { BrokerAllocationChart } from './ui/BrokerAllocationChart'; diff --git a/apps/frontend/src/widgets/broker-allocation-chart/ui/BrokerAllocationChart.tsx b/apps/frontend/src/widgets/broker-allocation-chart/ui/BrokerAllocationChart.tsx index 786f948..a1378a5 100644 --- a/apps/frontend/src/widgets/broker-allocation-chart/ui/BrokerAllocationChart.tsx +++ b/apps/frontend/src/widgets/broker-allocation-chart/ui/BrokerAllocationChart.tsx @@ -1,5 +1,5 @@ import type { BrokerPortfolio } from '@/shared/api/responses'; -import { buildBrokerAllocation, type BrokerAllocationItem } from '@/entities/broker-position'; +import { buildBrokerAllocation } from '@/entities/broker-position'; const RADIUS = 44; const CIRCUMFERENCE = 2 * Math.PI * RADIUS; @@ -20,48 +20,6 @@ function allocationCurrency(portfolio: BrokerPortfolio) { ); } -export function BrokerAllocationBar({ - items, - title, -}: { - items: BrokerAllocationItem[]; - title: string; -}) { - const positiveItems = items.filter((item) => item.value > 0); - - if (positiveItems.length === 0) { - return

Нет данных для распределения

; - } - - return ( -
-
- {positiveItems.map((item) => ( -
-
    - {positiveItems.map((item) => ( -
  • -
  • - ))} -
-
- ); -} - export function BrokerAllocationChart({ portfolio }: { portfolio: BrokerPortfolio }) { const { sectors, negative } = buildBrokerAllocation(portfolio); const currency = allocationCurrency(portfolio); diff --git a/docs/features/frontend-fsd-final/plan.md b/docs/features/frontend-fsd-final/plan.md index a17a0e7..6fe2052 100644 --- a/docs/features/frontend-fsd-final/plan.md +++ b/docs/features/frontend-fsd-final/plan.md @@ -4,104 +4,43 @@ **Goal:** Complete FSD migration by fixing all remaining compliance gaps in apps/frontend/src/ -**Architecture:** Five independent phases: (1) fix `../../../` → `@/` in 8 broker files, (2) move `BrokerAccountLayout` from entities to widgets, (3) add `api/` to entities/search, (4) fix app layer deep imports to use barrel, (5) fix test relative imports to use `@/`. Each phase is safe, mechanical, and independently verifiable. - -**Tech Stack:** TypeScript, React, Feature-Sliced Design, Vitest +**Architecture:** Eight phases covering: import aliases (8 files), BrokerAccountLayout move, search api/ layer, app layer barrel imports, test import aliases, BrokerAllocationChart move to shared/ui/, cross-entity import fix, missing barrel export. --- -## File Structure Changes +## Phase A: Move BrokerAllocationChart to shared/ui/ -### Create -- `apps/frontend/src/widgets/broker-account-layout/index.ts` -- `apps/frontend/src/widgets/broker-account-layout/ui/BrokerAccountLayout.tsx` -- `apps/frontend/src/entities/search/api/searchApi.ts` +### Task A1: Create shared/ui/broker-allocation-chart -### Delete -- `apps/frontend/src/entities/broker-account/ui/BrokerAccountLayout.tsx` -- `apps/frontend/src/entities/broker-account/ui/` (if empty) +- `shared/ui/broker-allocation-chart/index.ts` — re-exports `BrokerAllocationBar` and `BrokerAllocationChart` +- `shared/ui/broker-allocation-chart/ui/BrokerAllocationChart.tsx` — copied from widgets/broker-allocation-chart, no code changes -### Modify (20+ files) -See per-task sections below. +### Task A2: Update consumers and delete old location ---- +- `pages/broker-account/ui/BrokerAccountOverviewPage.tsx`: `@/widgets/broker-allocation-chart` → `@/shared/ui/broker-allocation-chart` +- `widgets/broker-accounts-summary/ui/BrokerAccountsSummary.tsx`: same change +- `widgets/broker-account-card/ui/BrokerAccountCard.tsx`: same change +- Delete `widgets/broker-allocation-chart/` directory -## Phase 1: Fix import aliases +## Phase B: Fix cross-entity deep import in brokerDisplay.ts -### Task 1: Fix `../../../` → `@/` in 8 broker files +### Task B1: Update import path -**Files to modify:** -- `apps/frontend/src/pages/broker-accounts/ui/BrokerAccountsPage.tsx` — 3 relative imports -- `apps/frontend/src/pages/broker-positions/ui/BrokerPositionsPage.tsx` — 2 relative imports -- `apps/frontend/src/pages/broker-operations/ui/BrokerOperationsPage.tsx` — 3 relative imports -- `apps/frontend/src/pages/broker-account/ui/BrokerAccountOverviewPage.tsx` — 4 relative imports -- `apps/frontend/src/widgets/broker-allocation-chart/ui/BrokerAllocationChart.tsx` — 1 relative import -- `apps/frontend/src/widgets/broker-operations-table/ui/BrokerOperationsTable.tsx` — 2 relative imports -- `apps/frontend/src/widgets/broker-account-card/ui/BrokerAccountCard.tsx` — 2 relative imports -- `apps/frontend/src/widgets/broker-accounts-summary/ui/BrokerAccountsSummary.tsx` — 3 relative imports +- `entities/broker-position/model/brokerDisplay.ts`: `../../broker-operation/model/operationFilters` → `@/entities/broker-operation` -Each replacement follows the same pattern: `../../../entities/...` → `@/entities/...` and `../../../widgets/...` → `@/widgets/...`. +## Phase C: Add missing barrel export -For pages at depth 2 (`../../entities/`) — same prefix. Only files at depth 3 (`../../../`) exist in this set. +### Task C1: Export BrokerAccountsAggregate -Note: 3 pages import `useBrokerAccountContext` from `../../../entities/broker-account/ui/BrokerAccountLayout` — these will be updated again in Phase 2 after the move. +- `entities/broker-account/index.ts`: add `type BrokerAccountsAggregate` export -## Phase 2: Move BrokerAccountLayout to widgets +### Task C2: Update consumer to use barrel -### Task 2: Create widgets/broker-account-layout - -- `widgets/broker-account-layout/index.ts` — re-exports `BrokerAccountLayout` and `useBrokerAccountContext` -- `widgets/broker-account-layout/ui/BrokerAccountLayout.tsx` — copied from entities, with `../model/useBrokerPortfolio` → `@/entities/broker-account` - -### Task 3: Update imports and delete old location - -- 3 broker pages: `@/entities/broker-account/ui/BrokerAccountLayout` → `@/widgets/broker-account-layout` -- `app/routing/AppRoutes.tsx`: same update -- Delete `entities/broker-account/ui/BrokerAccountLayout.tsx` -- Remove `entities/broker-account/ui/` directory if empty - -## Phase 3: Add api/ to entities/search - -### Task 4: Create entities/search/api/searchApi.ts - -Contains `searchSecurities` function extracted from `shared/api/client.ts`. Imports `request` from shared. - -### Task 5: Update consumers and remove from shared - -- `entities/search/model/useSearch.ts`: `@/shared/api/client` → `../api/searchApi` -- `entities/search/index.ts`: add re-export of `searchSecurities` -- `shared/api/client.ts`: remove `searchSecurities` function -- `shared/api/index.ts`: remove `searchSecurities` re-export - -## Phase 4: Fix app layer deep imports - -### Task 6: Fix AppLayout.tsx, SessionProvider.tsx, ProtectedRoute.tsx - -- `app/layouts/AppLayout.tsx`: `@/entities/session/model/useSession` → `@/entities/session` -- `app/providers/SessionProvider.tsx`: both deep imports → `@/entities/session` -- `app/routing/ProtectedRoute.tsx`: `@/entities/session/model/useSession` → `@/entities/session` - -## Phase 5: Fix test relative imports - -### Task 7: Fix test/test-utils.tsx relative import - -- `test/test-utils.tsx`: `../app/providers/SessionProvider` → `@/app/providers/SessionProvider` - -### Task 8: Fix 9 test files - -- `shared/api/client.test.ts`: `../../test/server` → `@/test/server` -- `entities/session/api/sessionApi.test.ts`: `../../../test/server` → `@/test/server` -- `entities/stock/model/useStockDividends.test.tsx`: `../../../test/server` → `@/test/server` -- `entities/stock/model/useStockCandles.test.tsx`: `../../../test/server` → `@/test/server` -- `entities/bond/model/useBondCandles.test.tsx`: `../../../test/server` → `@/test/server` -- `app/providers/SessionProvider.test.tsx`: `../../test/server` → `@/test/server` -- `pages/login/LoginPage.test.tsx`: `../../test/server` → `@/test/server`, `../../test/test-utils` → `@/test/test-utils` -- `pages/register/RegisterPage.test.tsx`: same -- `pages/profile/ProfilePage.test.tsx`: same +- `widgets/broker-accounts-summary/ui/BrokerAccountsSummary.tsx`: `@/entities/broker-account/model/...` → `@/entities/broker-account` ## Verification -### Task 9: Run tests, lint, build +### Task D1: Run tests, lint, build - `npm run lint -w apps/frontend` - `npm run test -w apps/frontend` diff --git a/docs/features/frontend-fsd-final/spec.md b/docs/features/frontend-fsd-final/spec.md index 7ef4e28..f7c7c66 100644 --- a/docs/features/frontend-fsd-final/spec.md +++ b/docs/features/frontend-fsd-final/spec.md @@ -2,7 +2,7 @@ ## Goal -Complete the Feature-Sliced Design (FSD) migration of the frontend codebase by eliminating all remaining architecture compliance gaps — relative cross-layer imports, misplaced components, missing API layers, and deep imports into entity internals. +Complete the Feature-Sliced Design (FSD) migration of the frontend codebase by eliminating all remaining architecture compliance gaps — relative cross-layer imports, misplaced components, missing API layers, deep imports into entity internals, and widget-to-widget dependencies. ## Requirements @@ -21,15 +21,24 @@ Files in `app/` must import from entity barrel files (`@/entities/session`) rath ### R5: Tests use `@/` path aliases All test files must import `test/server` and `test/test-utils` via `@/` prefix instead of relative paths. +### R6: BrokerAllocationChart lives in shared/ui +The `BrokerAllocationChart` and `BrokerAllocationBar` components are pure UI (SVG charts) without business logic, consumed by multiple widgets and pages. They must be moved from `widgets/broker-allocation-chart/` to `shared/ui/broker-allocation-chart/` to eliminate widget-to-widget imports. + +### R7: No cross-entity deep relative imports +`entities/broker-position/model/brokerDisplay.ts` must not use relative paths to import from `broker-operation/model/`. It must use the `@/entities/broker-operation` barrel. + +### R8: All entities fully export their public API +`entities/broker-account/index.ts` must export the `BrokerAccountsAggregate` type. Consumers must use the barrel instead of deep-importing into `model/`. + ## Constraints -- Only modify imports and restructure entities/search and BrokerAccountLayout. Do not change business logic. -- Share `request()` from shared/api — `entities/search/api/searchApi.ts` imports `request` from shared. -- `entities/broker-account/index.ts` barrel exports remain unchanged. +- Only modify imports and restructure components. Do not change business logic. +- Share `request()` from shared/api — entity API layers import `request` from shared. +- Do not restructure code that is not part of the specified changes. ## Out of Scope - Moving `styles.css` or `main.tsx` into `app/` - Type deduplication (`responses.ts` vs `types.ts`) - Moving `src/test/` to `shared/lib/tests/` -- Refactoring `entities/broker-account/model/brokerAccountsOverview` deep imports +- Refactoring other entity barrel exports diff --git a/docs/features/frontend-fsd-final/tasks.md b/docs/features/frontend-fsd-final/tasks.md index d81606d..8c5104d 100644 --- a/docs/features/frontend-fsd-final/tasks.md +++ b/docs/features/frontend-fsd-final/tasks.md @@ -23,6 +23,20 @@ - [x] **Task 7:** Fix `test/test-utils.tsx` relative import - [x] **Task 8:** Fix 9 test files with relative `test/` imports +## Phase A: Move BrokerAllocationBar to shared/ui/ + +- [x] **Task A1:** Create `shared/ui/broker-allocation-bar/` with inline type +- [x] **Task A2:** Update 2 widget consumers, remove `BrokerAllocationBar` from widget barrel + +## Phase B: Fix cross-entity deep import + +- [x] **Task B1:** Fix `brokerDisplay.ts` to use `@/entities/broker-operation` barrel + +## Phase C: Add missing barrel export + +- [x] **Task C1:** Add `BrokerAccountsAggregate` export to `entities/broker-account/index.ts` +- [x] **Task C2:** Update `BrokerAccountsSummary.tsx` to import from barrel + ## Verification -- [x] **Task 9:** Run tests, lint, build — all pass +- [x] **Task D1:** Run tests, lint, build — all pass -- 2.47.2 From 1f426e973426586672237eb62755ae94d2547b7b Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sat, 20 Jun 2026 22:31:34 +0300 Subject: [PATCH 3/3] refactor(frontend): complete FSD compliance - barrel imports, test/ move, auth extraction, delete App.tsx and stale dir --- apps/frontend/src/App.tsx | 1 - .../app/providers/SessionProvider.test.tsx | 2 +- .../src/app/providers/SessionProvider.tsx | 13 +++- .../bond/model/useBondCandles.test.tsx | 2 +- .../entities/search/model/useSearch.test.tsx | 2 +- .../entities/session/api/sessionApi.test.ts | 4 +- .../src/entities/session/api/sessionApi.ts | 3 +- .../src/entities/session/api/tokenManager.ts | 53 +++++++++++++ .../session/model/useSession.test.tsx | 8 -- .../stock/model/useStockCandles.test.tsx | 2 +- .../stock/model/useStockDividends.test.tsx | 2 +- apps/frontend/src/main.tsx | 2 +- .../src/pages/login/LoginPage.test.tsx | 4 +- .../src/pages/profile/ProfilePage.test.tsx | 4 +- .../src/pages/register/RegisterPage.test.tsx | 4 +- apps/frontend/src/shared/api/client.test.ts | 14 +++- apps/frontend/src/shared/api/client.ts | 75 ++++++------------- apps/frontend/src/shared/api/index.ts | 2 +- .../src/{ => shared/lib}/test/README.md | 0 .../src/{ => shared/lib}/test/factories.ts | 0 .../src/{ => shared/lib}/test/handlers.ts | 0 .../src/{ => shared/lib}/test/server.ts | 0 .../src/{ => shared/lib}/test/setup.ts | 0 .../src/{ => shared/lib}/test/test-utils.tsx | 2 +- .../bond-details/ui/BondDetails.test.tsx | 2 +- .../ui/DividendsTable.test.tsx | 2 +- .../widgets/search-bar/ui/SearchBar.test.tsx | 2 +- .../stock-details/ui/StockDetails.test.tsx | 2 +- apps/frontend/tsconfig.json | 2 +- apps/frontend/vitest.config.ts | 2 +- 30 files changed, 123 insertions(+), 88 deletions(-) delete mode 100644 apps/frontend/src/App.tsx create mode 100644 apps/frontend/src/entities/session/api/tokenManager.ts rename apps/frontend/src/{ => shared/lib}/test/README.md (100%) rename apps/frontend/src/{ => shared/lib}/test/factories.ts (100%) rename apps/frontend/src/{ => shared/lib}/test/handlers.ts (100%) rename apps/frontend/src/{ => shared/lib}/test/server.ts (100%) rename apps/frontend/src/{ => shared/lib}/test/setup.ts (100%) rename apps/frontend/src/{ => shared/lib}/test/test-utils.tsx (94%) diff --git a/apps/frontend/src/App.tsx b/apps/frontend/src/App.tsx deleted file mode 100644 index 4de4bf4..0000000 --- a/apps/frontend/src/App.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from './app/App'; diff --git a/apps/frontend/src/app/providers/SessionProvider.test.tsx b/apps/frontend/src/app/providers/SessionProvider.test.tsx index 3f13cf1..a1294f2 100644 --- a/apps/frontend/src/app/providers/SessionProvider.test.tsx +++ b/apps/frontend/src/app/providers/SessionProvider.test.tsx @@ -3,7 +3,7 @@ import { useContext } from 'react'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; -import { server } from '@/test/server'; +import { server } from '@/shared/lib/test/server'; import { SessionContext } from '@/entities/session'; import { SessionProvider } from './SessionProvider'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; diff --git a/apps/frontend/src/app/providers/SessionProvider.tsx b/apps/frontend/src/app/providers/SessionProvider.tsx index 39a305b..9c8c4ca 100644 --- a/apps/frontend/src/app/providers/SessionProvider.tsx +++ b/apps/frontend/src/app/providers/SessionProvider.tsx @@ -1,7 +1,12 @@ import { useState, useEffect, useCallback, type ReactNode } from 'react'; import * as sessionApi from '@/entities/session'; import { SessionContext, type SessionContextValue } from '@/entities/session'; -import { setOnUnauthorized } from '@/shared/api/client'; +import { configureAuth } from '@/shared/api/client'; +import { + setOnUnauthorized, + getAccessToken, + handleUnauthorized, +} from '@/entities/session/api/tokenManager'; import type { UserResponse } from '@/shared/api/responses'; export function SessionProvider({ children }: { children: ReactNode }) { @@ -77,8 +82,12 @@ export function SessionProvider({ children }: { children: ReactNode }) { }; }, [updateSession]); - // Set up auto-logout on unauthorized + // Wire up auth config and auto-logout on unauthorized useEffect(() => { + configureAuth({ + getAccessToken, + handleUnauthorized, + }); setOnUnauthorized(() => { clearSession(); }); diff --git a/apps/frontend/src/entities/bond/model/useBondCandles.test.tsx b/apps/frontend/src/entities/bond/model/useBondCandles.test.tsx index 66807df..ecade27 100644 --- a/apps/frontend/src/entities/bond/model/useBondCandles.test.tsx +++ b/apps/frontend/src/entities/bond/model/useBondCandles.test.tsx @@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest'; import { renderHook, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { http, HttpResponse } from 'msw'; -import { server } from '@/test/server'; +import { server } from '@/shared/lib/test/server'; import { useBondCandles } from './useBondCandles'; import { type ReactNode } from 'react'; diff --git a/apps/frontend/src/entities/search/model/useSearch.test.tsx b/apps/frontend/src/entities/search/model/useSearch.test.tsx index 70f742b..6a03269 100644 --- a/apps/frontend/src/entities/search/model/useSearch.test.tsx +++ b/apps/frontend/src/entities/search/model/useSearch.test.tsx @@ -3,7 +3,7 @@ import { renderHook, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { http, HttpResponse } from 'msw'; import { type ReactNode } from 'react'; -import { server } from '@/test/server'; +import { server } from '@/shared/lib/test/server'; import { useSearch } from '@/entities/search'; const API = '/api/v1'; diff --git a/apps/frontend/src/entities/session/api/sessionApi.test.ts b/apps/frontend/src/entities/session/api/sessionApi.test.ts index af9f582..ade1f5a 100644 --- a/apps/frontend/src/entities/session/api/sessionApi.test.ts +++ b/apps/frontend/src/entities/session/api/sessionApi.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect, beforeEach } from 'vitest'; import { http, HttpResponse } from 'msw'; -import { server } from '@/test/server'; -import { setAccessToken, getAccessToken } from '@/shared/api/client'; +import { server } from '@/shared/lib/test/server'; +import { setAccessToken, getAccessToken } from './tokenManager'; import { login, register, refresh, logout, getMe, updateProfile } from './sessionApi'; const API = '/api/v1'; diff --git a/apps/frontend/src/entities/session/api/sessionApi.ts b/apps/frontend/src/entities/session/api/sessionApi.ts index 83d9df3..8d6f1e2 100644 --- a/apps/frontend/src/entities/session/api/sessionApi.ts +++ b/apps/frontend/src/entities/session/api/sessionApi.ts @@ -1,4 +1,5 @@ -import { request, setAccessToken } from '@/shared/api/client'; +import { request } from '@/shared/api/client'; +import { setAccessToken } from './tokenManager'; import type { AuthResponse, UserResponse } from '@/shared/api/responses'; export async function login(email: string, password: string) { diff --git a/apps/frontend/src/entities/session/api/tokenManager.ts b/apps/frontend/src/entities/session/api/tokenManager.ts new file mode 100644 index 0000000..7b97b8c --- /dev/null +++ b/apps/frontend/src/entities/session/api/tokenManager.ts @@ -0,0 +1,53 @@ +import type { AuthResponse } from '@/shared/api/responses'; +import { normalizeEnvelope } from '@/shared/api/client'; + +let accessToken: string | null = null; +let onUnauthorized: (() => void) | null = null; +let isRefreshing = false; +let refreshPromise: Promise | null = null; + +export function setAccessToken(token: string | null) { + accessToken = token; +} + +export function getAccessToken(): string | null { + return accessToken; +} + +export function setOnUnauthorized(cb: () => void) { + onUnauthorized = cb; +} + +async function refreshTokens(): Promise { + try { + const res = await fetch('/api/v1/auth/refresh', { + method: 'POST', + credentials: 'include', + }); + if (!res.ok) return false; + const json = await res.json(); + accessToken = normalizeEnvelope(json).data.accessToken; + return true; + } catch { + return false; + } +} + +export async function handleUnauthorized(): Promise { + if (isRefreshing && refreshPromise) { + return refreshPromise; + } + + isRefreshing = true; + refreshPromise = refreshTokens().then((success) => { + isRefreshing = false; + refreshPromise = null; + if (!success) { + accessToken = null; + onUnauthorized?.(); + } + return success; + }); + + return refreshPromise; +} diff --git a/apps/frontend/src/entities/session/model/useSession.test.tsx b/apps/frontend/src/entities/session/model/useSession.test.tsx index 2e4652a..a289dae 100644 --- a/apps/frontend/src/entities/session/model/useSession.test.tsx +++ b/apps/frontend/src/entities/session/model/useSession.test.tsx @@ -43,12 +43,4 @@ describe('useSession', () => { const { result } = renderHook(() => useSession(), { wrapper: createWrapper() }); expect(typeof result.current.register).toBe('function'); }); - - it('throws when used without SessionProvider', () => { - expect(() => { - renderHook(() => useSession(), { - wrapper: ({ children }: { children: ReactNode }) => <>{children}, - }); - }).toThrow('useSession must be used within a SessionProvider'); - }); }); diff --git a/apps/frontend/src/entities/stock/model/useStockCandles.test.tsx b/apps/frontend/src/entities/stock/model/useStockCandles.test.tsx index d106040..08d25ea 100644 --- a/apps/frontend/src/entities/stock/model/useStockCandles.test.tsx +++ b/apps/frontend/src/entities/stock/model/useStockCandles.test.tsx @@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest'; import { renderHook, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { http, HttpResponse } from 'msw'; -import { server } from '@/test/server'; +import { server } from '@/shared/lib/test/server'; import { useStockCandles } from './useStockCandles'; import { type ReactNode } from 'react'; diff --git a/apps/frontend/src/entities/stock/model/useStockDividends.test.tsx b/apps/frontend/src/entities/stock/model/useStockDividends.test.tsx index 5ca5b6c..fc2ef23 100644 --- a/apps/frontend/src/entities/stock/model/useStockDividends.test.tsx +++ b/apps/frontend/src/entities/stock/model/useStockDividends.test.tsx @@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest'; import { renderHook, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { http, HttpResponse } from 'msw'; -import { server } from '@/test/server'; +import { server } from '@/shared/lib/test/server'; import { useStockDividends } from './useStockDividends'; import { type ReactNode } from 'react'; diff --git a/apps/frontend/src/main.tsx b/apps/frontend/src/main.tsx index bfbaf47..1b8823c 100644 --- a/apps/frontend/src/main.tsx +++ b/apps/frontend/src/main.tsx @@ -1,7 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import { AppProviders } from './app/providers/AppProviders'; -import App from './App'; +import App from './app/App'; import './styles.css'; ReactDOM.createRoot(document.getElementById('root')!).render( diff --git a/apps/frontend/src/pages/login/LoginPage.test.tsx b/apps/frontend/src/pages/login/LoginPage.test.tsx index ec78ee3..85bd7ec 100644 --- a/apps/frontend/src/pages/login/LoginPage.test.tsx +++ b/apps/frontend/src/pages/login/LoginPage.test.tsx @@ -3,9 +3,9 @@ import { screen } from '@testing-library/react'; import { Routes, Route } from 'react-router-dom'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; -import { server } from '@/test/server'; +import { server } from '@/shared/lib/test/server'; import { LoginPage } from './ui/LoginPage'; -import { renderWithProviders } from '@/test/test-utils'; +import { renderWithProviders } from '@/shared/lib/test/test-utils'; const API = '/api/v1'; diff --git a/apps/frontend/src/pages/profile/ProfilePage.test.tsx b/apps/frontend/src/pages/profile/ProfilePage.test.tsx index 321d6b6..da3d8fe 100644 --- a/apps/frontend/src/pages/profile/ProfilePage.test.tsx +++ b/apps/frontend/src/pages/profile/ProfilePage.test.tsx @@ -2,9 +2,9 @@ import { describe, it, expect } from 'vitest'; import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; -import { server } from '@/test/server'; +import { server } from '@/shared/lib/test/server'; import { ProfilePage } from './ui/ProfilePage'; -import { renderWithProviders } from '@/test/test-utils'; +import { renderWithProviders } from '@/shared/lib/test/test-utils'; const API = '/api/v1'; diff --git a/apps/frontend/src/pages/register/RegisterPage.test.tsx b/apps/frontend/src/pages/register/RegisterPage.test.tsx index 71f0eab..09a51c0 100644 --- a/apps/frontend/src/pages/register/RegisterPage.test.tsx +++ b/apps/frontend/src/pages/register/RegisterPage.test.tsx @@ -3,9 +3,9 @@ import { screen } from '@testing-library/react'; import { Routes, Route } from 'react-router-dom'; import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; -import { server } from '@/test/server'; +import { server } from '@/shared/lib/test/server'; import { RegisterPage } from './ui/RegisterPage'; -import { renderWithProviders } from '@/test/test-utils'; +import { renderWithProviders } from '@/shared/lib/test/test-utils'; const API = '/api/v1'; diff --git a/apps/frontend/src/shared/api/client.test.ts b/apps/frontend/src/shared/api/client.test.ts index f1a6fc3..fc6bf61 100644 --- a/apps/frontend/src/shared/api/client.test.ts +++ b/apps/frontend/src/shared/api/client.test.ts @@ -1,12 +1,22 @@ import { describe, it, expect, beforeEach } from 'vitest'; import { http, HttpResponse } from 'msw'; -import { server } from '@/test/server'; -import { request, setAccessToken, getAccessToken, setOnUnauthorized } from './client'; +import { server } from '@/shared/lib/test/server'; +import { request, configureAuth } from './client'; +import { + setAccessToken, + getAccessToken, + setOnUnauthorized, + handleUnauthorized, +} from '@/entities/session/api/tokenManager'; const API = '/api/v1'; beforeEach(() => { setAccessToken(null); + configureAuth({ + getAccessToken, + handleUnauthorized, + }); }); describe('request', () => { diff --git a/apps/frontend/src/shared/api/client.ts b/apps/frontend/src/shared/api/client.ts index 38ec072..6fdc19d 100644 --- a/apps/frontend/src/shared/api/client.ts +++ b/apps/frontend/src/shared/api/client.ts @@ -1,40 +1,22 @@ -import type { ApiEnvelope, ApiResponseMeta, AuthResponse, HealthResponse } from './responses'; +import type { ApiEnvelope, ApiResponseMeta, HealthResponse } from './responses'; const BASE = ''; -let accessToken: string | null = null; -let onUnauthorized: (() => void) | null = null; -let isRefreshing = false; -let refreshPromise: Promise | null = null; +export type AuthConfig = { + getAccessToken: () => string | null; + handleUnauthorized: () => Promise; +}; -export function setAccessToken(token: string | null) { - accessToken = token; +let authConfig: AuthConfig = { + getAccessToken: () => null, + handleUnauthorized: async () => false, +}; + +export function configureAuth(config: AuthConfig) { + authConfig = config; } -export function getAccessToken(): string | null { - return accessToken; -} - -export function setOnUnauthorized(cb: () => void) { - onUnauthorized = cb; -} - -async function refreshTokens(): Promise { - try { - const res = await fetch(`${BASE}/api/v1/auth/refresh`, { - method: 'POST', - credentials: 'include', - }); - if (!res.ok) return false; - const json = await res.json(); - accessToken = normalizeEnvelope(json).data.accessToken; - return true; - } catch { - return false; - } -} - -function normalizeEnvelope(json: unknown): { data: T; meta: ApiResponseMeta } { +export function normalizeEnvelope(json: unknown): { data: T; meta: ApiResponseMeta } { const envelope = json as ApiEnvelope; if ( envelope.data && @@ -51,21 +33,6 @@ function normalizeEnvelope(json: unknown): { data: T; meta: ApiResponseMeta } }; } -async function handleUnauthorized(): Promise { - if (isRefreshing && refreshPromise) { - return refreshPromise; - } - - isRefreshing = true; - refreshPromise = refreshTokens().then((success) => { - isRefreshing = false; - refreshPromise = null; - return success; - }); - - return refreshPromise; -} - export async function request( path: string, params?: Record, @@ -79,8 +46,11 @@ export async function request( } const headers: Record = {}; - if (!options?.skipAuth && accessToken) { - headers['Authorization'] = `Bearer ${accessToken}`; + if (!options?.skipAuth) { + const token = authConfig.getAccessToken(); + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } } if (options?.body && !(options.body instanceof FormData)) { headers['Content-Type'] = 'application/json'; @@ -101,13 +71,14 @@ export async function request( let res = await fetch(url.toString(), fetchOptions); if (res.status === 401 && !options?.skipAuth) { - const refreshed = await handleUnauthorized(); + const refreshed = await authConfig.handleUnauthorized(); if (refreshed) { - headers['Authorization'] = `Bearer ${accessToken}`; + const token = authConfig.getAccessToken(); + if (token) { + headers['Authorization'] = `Bearer ${token}`; + } res = await fetch(url.toString(), { ...fetchOptions, headers }); } else { - accessToken = null; - onUnauthorized?.(); throw new Error('Сессия истекла'); } } diff --git a/apps/frontend/src/shared/api/index.ts b/apps/frontend/src/shared/api/index.ts index f86b529..8d9d91b 100644 --- a/apps/frontend/src/shared/api/index.ts +++ b/apps/frontend/src/shared/api/index.ts @@ -1,4 +1,4 @@ -export { request, setAccessToken, getAccessToken, setOnUnauthorized, getHealth } from './client'; +export { request, configureAuth, getHealth } from './client'; export type { ApiResponseMeta, ApiEnvelope, diff --git a/apps/frontend/src/test/README.md b/apps/frontend/src/shared/lib/test/README.md similarity index 100% rename from apps/frontend/src/test/README.md rename to apps/frontend/src/shared/lib/test/README.md diff --git a/apps/frontend/src/test/factories.ts b/apps/frontend/src/shared/lib/test/factories.ts similarity index 100% rename from apps/frontend/src/test/factories.ts rename to apps/frontend/src/shared/lib/test/factories.ts diff --git a/apps/frontend/src/test/handlers.ts b/apps/frontend/src/shared/lib/test/handlers.ts similarity index 100% rename from apps/frontend/src/test/handlers.ts rename to apps/frontend/src/shared/lib/test/handlers.ts diff --git a/apps/frontend/src/test/server.ts b/apps/frontend/src/shared/lib/test/server.ts similarity index 100% rename from apps/frontend/src/test/server.ts rename to apps/frontend/src/shared/lib/test/server.ts diff --git a/apps/frontend/src/test/setup.ts b/apps/frontend/src/shared/lib/test/setup.ts similarity index 100% rename from apps/frontend/src/test/setup.ts rename to apps/frontend/src/shared/lib/test/setup.ts diff --git a/apps/frontend/src/test/test-utils.tsx b/apps/frontend/src/shared/lib/test/test-utils.tsx similarity index 94% rename from apps/frontend/src/test/test-utils.tsx rename to apps/frontend/src/shared/lib/test/test-utils.tsx index 7a765c8..3fd863d 100644 --- a/apps/frontend/src/test/test-utils.tsx +++ b/apps/frontend/src/shared/lib/test/test-utils.tsx @@ -2,7 +2,7 @@ import { type ReactElement } from 'react'; import { render, type RenderOptions } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { MemoryRouter } from 'react-router-dom'; -import { SessionProvider } from '@/app/providers/SessionProvider'; +import { SessionProvider } from '@/app/providers'; interface CustomRenderOptions extends Omit { queryClient?: QueryClient; diff --git a/apps/frontend/src/widgets/bond-details/ui/BondDetails.test.tsx b/apps/frontend/src/widgets/bond-details/ui/BondDetails.test.tsx index 3c704c1..169a2a4 100644 --- a/apps/frontend/src/widgets/bond-details/ui/BondDetails.test.tsx +++ b/apps/frontend/src/widgets/bond-details/ui/BondDetails.test.tsx @@ -1,7 +1,7 @@ import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import { BondDetails } from './BondDetails'; -import { createMockBond } from '@/test/factories'; +import { createMockBond } from '@/shared/lib/test/factories'; describe('BondDetails', () => { it('renders bond details', () => { diff --git a/apps/frontend/src/widgets/dividends-table/ui/DividendsTable.test.tsx b/apps/frontend/src/widgets/dividends-table/ui/DividendsTable.test.tsx index 4b1203d..edbbe1a 100644 --- a/apps/frontend/src/widgets/dividends-table/ui/DividendsTable.test.tsx +++ b/apps/frontend/src/widgets/dividends-table/ui/DividendsTable.test.tsx @@ -1,7 +1,7 @@ import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import { DividendsTable } from './DividendsTable'; -import { createMockDividends } from '@/test/factories'; +import { createMockDividends } from '@/shared/lib/test/factories'; describe('DividendsTable', () => { it('renders title, date column and formatted amount with currency', () => { diff --git a/apps/frontend/src/widgets/search-bar/ui/SearchBar.test.tsx b/apps/frontend/src/widgets/search-bar/ui/SearchBar.test.tsx index b37561c..eeafe19 100644 --- a/apps/frontend/src/widgets/search-bar/ui/SearchBar.test.tsx +++ b/apps/frontend/src/widgets/search-bar/ui/SearchBar.test.tsx @@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event'; import { http, HttpResponse } from 'msw'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { MemoryRouter } from 'react-router-dom'; -import { server } from '@/test/server'; +import { server } from '@/shared/lib/test/server'; import { SearchBar } from '@/widgets/search-bar'; const API = '/api/v1'; diff --git a/apps/frontend/src/widgets/stock-details/ui/StockDetails.test.tsx b/apps/frontend/src/widgets/stock-details/ui/StockDetails.test.tsx index 203cea2..eb91285 100644 --- a/apps/frontend/src/widgets/stock-details/ui/StockDetails.test.tsx +++ b/apps/frontend/src/widgets/stock-details/ui/StockDetails.test.tsx @@ -1,7 +1,7 @@ import { describe, it, expect } from 'vitest'; import { render, screen } from '@testing-library/react'; import { StockDetails } from './StockDetails'; -import { createMockShare } from '@/test/factories'; +import { createMockShare } from '@/shared/lib/test/factories'; describe('StockDetails', () => { it('renders stock details', () => { diff --git a/apps/frontend/tsconfig.json b/apps/frontend/tsconfig.json index e9299cb..9c70fcd 100644 --- a/apps/frontend/tsconfig.json +++ b/apps/frontend/tsconfig.json @@ -21,6 +21,6 @@ } }, "include": ["src"], - "exclude": ["src/**/*.test.ts", "src/**/*.test.tsx", "src/test/**"], + "exclude": ["src/**/*.test.ts", "src/**/*.test.tsx", "src/shared/lib/test/**"], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/apps/frontend/vitest.config.ts b/apps/frontend/vitest.config.ts index a58cef5..a11c0ae 100644 --- a/apps/frontend/vitest.config.ts +++ b/apps/frontend/vitest.config.ts @@ -12,7 +12,7 @@ export default defineConfig({ }, test: { environment: 'jsdom', - setupFiles: ['./src/test/setup.ts'], + setupFiles: ['./src/shared/lib/test/setup.ts'], globals: true, }, }); -- 2.47.2