feat: complete Phase 3 API type unification, delete stale test file
- Delete shared/api/responses.ts, move type re-exports to index.ts - Update all 55+ imports from shared/api/responses to shared/api - Delete stale client.test.ts (tested deleted client.ts) - Run biome checks and fix import ordering - Update tasks.md and plan.md to reflect actual approach
This commit is contained in:
parent
063e80c375
commit
9932278b64
@ -6,8 +6,8 @@ import {
|
|||||||
handleUnauthorized,
|
handleUnauthorized,
|
||||||
setOnUnauthorized,
|
setOnUnauthorized,
|
||||||
} from '@/entities/session/api/tokenManager'
|
} from '@/entities/session/api/tokenManager'
|
||||||
|
import type { UserResponse } from '@/shared/api'
|
||||||
import { configureKyAuth } from '@/shared/api/kyClient'
|
import { configureKyAuth } from '@/shared/api/kyClient'
|
||||||
import type { UserResponse } from '@/shared/api/responses'
|
|
||||||
|
|
||||||
export function SessionProvider({ children }: { children: ReactNode }) {
|
export function SessionProvider({ children }: { children: ReactNode }) {
|
||||||
const [user, setUser] = useState<UserResponse | null>(null)
|
const [user, setUser] = useState<UserResponse | null>(null)
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { request } from '@/shared/api/kyClient'
|
|
||||||
import type {
|
import type {
|
||||||
ApiResponseMeta,
|
ApiResponseMeta,
|
||||||
BondHistoryItem,
|
BondHistoryItem,
|
||||||
BondMarketData,
|
BondMarketData,
|
||||||
BondResponse,
|
BondResponse,
|
||||||
CandleItem,
|
CandleItem,
|
||||||
} from '@/shared/api/responses'
|
} from '@/shared/api'
|
||||||
|
import { request } from '@/shared/api/kyClient'
|
||||||
|
|
||||||
export function getBond(secid: string): Promise<{ data: BondResponse; meta: ApiResponseMeta }> {
|
export function getBond(secid: string): Promise<{ data: BondResponse; meta: ApiResponseMeta }> {
|
||||||
return request<BondResponse>(`/api/v1/securities/bonds/${encodeURIComponent(secid)}`)
|
return request<BondResponse>(`/api/v1/securities/bonds/${encodeURIComponent(secid)}`)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import type { BondResponse } from '@/shared/api/responses'
|
import type { BondResponse } from '@/shared/api'
|
||||||
import { getBond } from '../api/bondApi'
|
import { getBond } from '../api/bondApi'
|
||||||
|
|
||||||
export function useBond(secid: string) {
|
export function useBond(secid: string) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import type { CandleItem } from '@/shared/api/responses'
|
import type { CandleItem } from '@/shared/api'
|
||||||
import { getBondCandles } from '../api/bondApi'
|
import { getBondCandles } from '../api/bondApi'
|
||||||
|
|
||||||
export function useBondCandles(secid: string, interval: '1h' | '24h', from: string, till: string) {
|
export function useBondCandles(secid: string, interval: '1h' | '24h', from: string, till: string) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
|
import type { ApiResponseMeta, BrokerAccount, BrokerPortfolio } from '@/shared/api'
|
||||||
import { request } from '@/shared/api/kyClient'
|
import { request } from '@/shared/api/kyClient'
|
||||||
import type { ApiResponseMeta, BrokerAccount, BrokerPortfolio } from '@/shared/api/responses'
|
|
||||||
|
|
||||||
export type BrokerOperationQuery = {
|
export type BrokerOperationQuery = {
|
||||||
from?: string
|
from?: string
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import type { BrokerPortfolio } from '@/shared/api/responses'
|
import type { BrokerPortfolio } from '@/shared/api'
|
||||||
import { aggregateBrokerAccounts } from '../model/brokerAccountsOverview'
|
import { aggregateBrokerAccounts } from '../model/brokerAccountsOverview'
|
||||||
|
|
||||||
function portfolio(
|
function portfolio(
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { BrokerMoney, BrokerPortfolio } from '@/shared/api/responses'
|
import type { BrokerMoney, BrokerPortfolio } from '@/shared/api'
|
||||||
|
|
||||||
export interface BrokerCurrencyAllocationSummary {
|
export interface BrokerCurrencyAllocationSummary {
|
||||||
shares: number
|
shares: number
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useQueries } from '@tanstack/react-query'
|
import { useQueries } from '@tanstack/react-query'
|
||||||
import type { BrokerAccount, BrokerPortfolio } from '@/shared/api/responses'
|
import type { BrokerAccount, BrokerPortfolio } from '@/shared/api'
|
||||||
import { getBrokerPortfolio } from '../api/brokerAccountApi'
|
import { getBrokerPortfolio } from '../api/brokerAccountApi'
|
||||||
|
|
||||||
export function useBrokerAccountPortfolios(accounts: BrokerAccount[]) {
|
export function useBrokerAccountPortfolios(accounts: BrokerAccount[]) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import type { BrokerAccount } from '@/shared/api/responses'
|
import type { BrokerAccount } from '@/shared/api'
|
||||||
import { getBrokerAccounts } from '../api/brokerAccountApi'
|
import { getBrokerAccounts } from '../api/brokerAccountApi'
|
||||||
|
|
||||||
export function useBrokerAccounts() {
|
export function useBrokerAccounts() {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import type { BrokerPortfolio } from '@/shared/api/responses'
|
import type { BrokerPortfolio } from '@/shared/api'
|
||||||
import { getBrokerPortfolio } from '../api/brokerAccountApi'
|
import { getBrokerPortfolio } from '../api/brokerAccountApi'
|
||||||
|
|
||||||
export function useBrokerPortfolio(accountId: string | undefined) {
|
export function useBrokerPortfolio(accountId: string | undefined) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
|
import type { ApiResponseMeta, BrokerEventsData } from '@/shared/api'
|
||||||
import { request } from '@/shared/api/kyClient'
|
import { request } from '@/shared/api/kyClient'
|
||||||
import type { ApiResponseMeta, BrokerEventsData } from '@/shared/api/responses'
|
|
||||||
|
|
||||||
export type BrokerEventsQuery = {
|
export type BrokerEventsQuery = {
|
||||||
from: string
|
from: string
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import type { BrokerEventsData } from '@/shared/api/responses'
|
import type { BrokerEventsData } from '@/shared/api'
|
||||||
import { type BrokerEventsQuery, getBrokerEvents } from '../api/brokerEventApi'
|
import { type BrokerEventsQuery, getBrokerEvents } from '../api/brokerEventApi'
|
||||||
|
|
||||||
export function useBrokerEvents(accountId: string | undefined, query: BrokerEventsQuery) {
|
export function useBrokerEvents(accountId: string | undefined, query: BrokerEventsQuery) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
|
import type { ApiResponseMeta, BrokerOperationsPage } from '@/shared/api'
|
||||||
import { request } from '@/shared/api/kyClient'
|
import { request } from '@/shared/api/kyClient'
|
||||||
import type { ApiResponseMeta, BrokerOperationsPage } from '@/shared/api/responses'
|
|
||||||
|
|
||||||
export type BrokerOperationQuery = {
|
export type BrokerOperationQuery = {
|
||||||
from?: string
|
from?: string
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { BrokerOperation } from '@/shared/api/responses'
|
import type { BrokerOperation } from '@/shared/api'
|
||||||
|
|
||||||
export type BrokerOperationImpact = 'adds' | 'reduces' | 'neutral' | 'unknown'
|
export type BrokerOperationImpact = 'adds' | 'reduces' | 'neutral' | 'unknown'
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { keepPreviousData, useQuery } from '@tanstack/react-query'
|
import { keepPreviousData, useQuery } from '@tanstack/react-query'
|
||||||
import type { BrokerOperationsPage } from '@/shared/api/responses'
|
import type { BrokerOperationsPage } from '@/shared/api'
|
||||||
import { type BrokerOperationQuery, getBrokerOperations } from '../api/brokerOperationApi'
|
import { type BrokerOperationQuery, getBrokerOperations } from '../api/brokerOperationApi'
|
||||||
|
|
||||||
export function useBrokerOperations(
|
export function useBrokerOperations(
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
|
import type { ApiResponseMeta, BrokerPositionsPage } from '@/shared/api'
|
||||||
import { request } from '@/shared/api/kyClient'
|
import { request } from '@/shared/api/kyClient'
|
||||||
import type { ApiResponseMeta, BrokerPositionsPage } from '@/shared/api/responses'
|
|
||||||
|
|
||||||
export function getBrokerPositions(
|
export function getBrokerPositions(
|
||||||
accountId: string,
|
accountId: string,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { describe, expect, it } from 'vitest'
|
import { describe, expect, it } from 'vitest'
|
||||||
import type { BrokerMoney, BrokerPortfolio } from '@/shared/api/responses'
|
import type { BrokerMoney, BrokerPortfolio } from '@/shared/api'
|
||||||
import { buildBrokerAllocation } from './brokerAllocation'
|
import { buildBrokerAllocation } from './brokerAllocation'
|
||||||
|
|
||||||
function money(value: number): BrokerMoney {
|
function money(value: number): BrokerMoney {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { BrokerPortfolio } from '@/shared/api/responses'
|
import type { BrokerPortfolio } from '@/shared/api'
|
||||||
|
|
||||||
export type BrokerAllocationKey = 'shares' | 'bonds' | 'etf' | 'cash' | 'other'
|
export type BrokerAllocationKey = 'shares' | 'bonds' | 'etf' | 'cash' | 'other'
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import {
|
|||||||
getBrokerOperationTypeLabel,
|
getBrokerOperationTypeLabel,
|
||||||
isBrokerOperationType,
|
isBrokerOperationType,
|
||||||
} from '@/entities/broker-operation'
|
} from '@/entities/broker-operation'
|
||||||
import type { BrokerOperation, BrokerPosition } from '@/shared/api/responses'
|
import type { BrokerOperation, BrokerPosition } from '@/shared/api'
|
||||||
import { getBrokerInstrumentPath, getBrokerPositionGroup } from './brokerDisplay'
|
import { getBrokerInstrumentPath, getBrokerPositionGroup } from './brokerDisplay'
|
||||||
|
|
||||||
function position(input: Partial<BrokerPosition>): BrokerPosition {
|
function position(input: Partial<BrokerPosition>): BrokerPosition {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { BrokerPosition } from '@/shared/api/responses'
|
import type { BrokerPosition } from '@/shared/api'
|
||||||
|
|
||||||
export type BrokerPositionGroup = 'shares' | 'bonds' | 'other'
|
export type BrokerPositionGroup = 'shares' | 'bonds' | 'other'
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { keepPreviousData, useQuery } from '@tanstack/react-query'
|
import { keepPreviousData, useQuery } from '@tanstack/react-query'
|
||||||
import type { BrokerPositionsPage } from '@/shared/api/responses'
|
import type { BrokerPositionsPage } from '@/shared/api'
|
||||||
import { getBrokerPositions } from '../api/brokerPositionApi'
|
import { getBrokerPositions } from '../api/brokerPositionApi'
|
||||||
|
|
||||||
export function useBrokerPositions(
|
export function useBrokerPositions(
|
||||||
|
|||||||
@ -1,10 +1,5 @@
|
|||||||
|
import type { AnalyticsResponse, Portfolio, PortfolioDetail, Position } from '@/shared/api'
|
||||||
import { request } from '@/shared/api/kyClient'
|
import { request } from '@/shared/api/kyClient'
|
||||||
import type {
|
|
||||||
AnalyticsResponse,
|
|
||||||
Portfolio,
|
|
||||||
PortfolioDetail,
|
|
||||||
Position,
|
|
||||||
} from '@/shared/api/responses'
|
|
||||||
|
|
||||||
export function getPortfolios(): Promise<{
|
export function getPortfolios(): Promise<{
|
||||||
data: Portfolio[]
|
data: Portfolio[]
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import type { PortfolioDetail } from '@/shared/api/responses'
|
import type { PortfolioDetail } from '@/shared/api'
|
||||||
import { getPortfolio } from '../api/portfolioApi'
|
import { getPortfolio } from '../api/portfolioApi'
|
||||||
|
|
||||||
export function usePortfolio(id: number) {
|
export function usePortfolio(id: number) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import type { AnalyticsResponse } from '@/shared/api/responses'
|
import type { AnalyticsResponse } from '@/shared/api'
|
||||||
import { getPortfolioAnalytics } from '../api/portfolioApi'
|
import { getPortfolioAnalytics } from '../api/portfolioApi'
|
||||||
|
|
||||||
export function usePortfolioAnalytics(portfolioId: number) {
|
export function usePortfolioAnalytics(portfolioId: number) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import type { Portfolio } from '@/shared/api/responses'
|
import type { Portfolio } from '@/shared/api'
|
||||||
import { getPortfolios } from '../api/portfolioApi'
|
import { getPortfolios } from '../api/portfolioApi'
|
||||||
|
|
||||||
export function usePortfolios() {
|
export function usePortfolios() {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||||
import type { PortfolioDetail } from '@/shared/api/responses'
|
import type { PortfolioDetail } from '@/shared/api'
|
||||||
import { addPosition, removePosition, updatePosition } from '../api/portfolioApi'
|
import { addPosition, removePosition, updatePosition } from '../api/portfolioApi'
|
||||||
|
|
||||||
export function usePositionMutations(portfolioId: number) {
|
export function usePositionMutations(portfolioId: number) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
|
import type { SearchResultItem } from '@/shared/api'
|
||||||
import { request } from '@/shared/api/kyClient'
|
import { request } from '@/shared/api/kyClient'
|
||||||
import type { SearchResultItem } from '@/shared/api/responses'
|
|
||||||
|
|
||||||
export function searchSecurities(q: string, type: 'all' | 'share' | 'bond' = 'all', limit = 20) {
|
export function searchSecurities(q: string, type: 'all' | 'share' | 'bond' = 'all', limit = 20) {
|
||||||
return request<SearchResultItem[]>('/api/v1/securities/search', {
|
return request<SearchResultItem[]>('/api/v1/securities/search', {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import type { SearchResultItem } from '@/shared/api/responses'
|
import type { SearchResultItem } from '@/shared/api'
|
||||||
import { searchSecurities } from '../api/searchApi'
|
import { searchSecurities } from '../api/searchApi'
|
||||||
|
|
||||||
export function useSearch(query: string) {
|
export function useSearch(query: string) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
|
import type { AuthResponse, UserResponse } from '@/shared/api'
|
||||||
import { request } from '@/shared/api/kyClient'
|
import { request } from '@/shared/api/kyClient'
|
||||||
import type { AuthResponse, UserResponse } from '@/shared/api/responses'
|
|
||||||
import { setAccessToken } from './tokenManager'
|
import { setAccessToken } from './tokenManager'
|
||||||
|
|
||||||
export async function login(email: string, password: string) {
|
export async function login(email: string, password: string) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
|
import type { AuthResponse } from '@/shared/api'
|
||||||
import { normalizeEnvelope } from '@/shared/api/kyClient'
|
import { normalizeEnvelope } from '@/shared/api/kyClient'
|
||||||
import type { AuthResponse } from '@/shared/api/responses'
|
|
||||||
|
|
||||||
let accessToken: string | null = null
|
let accessToken: string | null = null
|
||||||
let onUnauthorized: (() => void) | null = null
|
let onUnauthorized: (() => void) | null = null
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { create } from 'zustand'
|
import { create } from 'zustand'
|
||||||
import type { UserResponse } from '@/shared/api/responses'
|
import type { UserResponse } from '@/shared/api'
|
||||||
|
|
||||||
interface SessionState {
|
interface SessionState {
|
||||||
user: UserResponse | null
|
user: UserResponse | null
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { request } from '@/shared/api/kyClient'
|
|
||||||
import type {
|
import type {
|
||||||
ApiResponseMeta,
|
ApiResponseMeta,
|
||||||
CandleItem,
|
CandleItem,
|
||||||
@ -6,7 +5,8 @@ import type {
|
|||||||
ShareHistoryItem,
|
ShareHistoryItem,
|
||||||
ShareResponse,
|
ShareResponse,
|
||||||
StockMarketData,
|
StockMarketData,
|
||||||
} from '@/shared/api/responses'
|
} from '@/shared/api'
|
||||||
|
import { request } from '@/shared/api/kyClient'
|
||||||
|
|
||||||
export function getShare(secid: string): Promise<{ data: ShareResponse; meta: ApiResponseMeta }> {
|
export function getShare(secid: string): Promise<{ data: ShareResponse; meta: ApiResponseMeta }> {
|
||||||
return request<ShareResponse>(`/api/v1/securities/shares/${encodeURIComponent(secid)}`)
|
return request<ShareResponse>(`/api/v1/securities/shares/${encodeURIComponent(secid)}`)
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import type { ShareResponse } from '@/shared/api/responses'
|
import type { ShareResponse } from '@/shared/api'
|
||||||
import { getShare } from '../api/stockApi'
|
import { getShare } from '../api/stockApi'
|
||||||
|
|
||||||
export function useStock(secid: string) {
|
export function useStock(secid: string) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import type { CandleItem } from '@/shared/api/responses'
|
import type { CandleItem } from '@/shared/api'
|
||||||
import { getShareCandles } from '../api/stockApi'
|
import { getShareCandles } from '../api/stockApi'
|
||||||
|
|
||||||
export function useStockCandles(secid: string, interval: '1h' | '24h', from: string, till: string) {
|
export function useStockCandles(secid: string, interval: '1h' | '24h', from: string, till: string) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from '@tanstack/react-query'
|
import { useQuery } from '@tanstack/react-query'
|
||||||
import type { DividendItem } from '@/shared/api/responses'
|
import type { DividendItem } from '@/shared/api'
|
||||||
import { getShareDividends } from '../api/stockApi'
|
import { getShareDividends } from '../api/stockApi'
|
||||||
|
|
||||||
export function useStockDividends(secid: string) {
|
export function useStockDividends(secid: string) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
|
import type { ScreenerResult } from '@/shared/api'
|
||||||
import { request } from '@/shared/api/kyClient'
|
import { request } from '@/shared/api/kyClient'
|
||||||
import type { ScreenerResult } from '@/shared/api/responses'
|
|
||||||
|
|
||||||
export interface ScreenerQuery {
|
export interface ScreenerQuery {
|
||||||
type: 'share' | 'bond'
|
type: 'share' | 'bond'
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Link } from '@tanstack/react-router'
|
import { Link } from '@tanstack/react-router'
|
||||||
import type { ScreenerResult } from '@/shared/api/responses'
|
import type { ScreenerResult } from '@/shared/api'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
result: ScreenerResult
|
result: ScreenerResult
|
||||||
|
|||||||
@ -1,163 +0,0 @@
|
|||||||
import { HttpResponse, http } from 'msw'
|
|
||||||
import { beforeEach, describe, expect, it } from 'vitest'
|
|
||||||
import {
|
|
||||||
getAccessToken,
|
|
||||||
handleUnauthorized,
|
|
||||||
setAccessToken,
|
|
||||||
setOnUnauthorized,
|
|
||||||
} from '@/entities/session/api/tokenManager'
|
|
||||||
import { server } from '@/shared/lib/test/server'
|
|
||||||
import { configureKyAuth, request } from './kyClient'
|
|
||||||
|
|
||||||
const API = '/api/v1'
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
setAccessToken(null)
|
|
||||||
configureKyAuth({
|
|
||||||
getAccessToken,
|
|
||||||
handleUnauthorized,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('request', () => {
|
|
||||||
it('makes GET request and returns data', async () => {
|
|
||||||
const result = await request<{ status: string; timestamp: string; uptime: number }>(
|
|
||||||
'/api/v1/health',
|
|
||||||
)
|
|
||||||
expect(result.data.status).toBe('ok')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('supports the single API envelope shape documented by Swagger', async () => {
|
|
||||||
server.use(
|
|
||||||
http.get(`${API}/test-single-envelope`, () =>
|
|
||||||
HttpResponse.json({
|
|
||||||
data: { ok: true },
|
|
||||||
meta: { fromCache: true, cachedAt: '2026-06-17T00:00:00.000Z' },
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
const result = await request<{ ok: boolean }>('/api/v1/test-single-envelope')
|
|
||||||
|
|
||||||
expect(result).toEqual({
|
|
||||||
data: { ok: true },
|
|
||||||
meta: { fromCache: true, cachedAt: '2026-06-17T00:00:00.000Z' },
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('includes Authorization header when token is set', async () => {
|
|
||||||
setAccessToken('test-token')
|
|
||||||
let capturedAuth: string | null = null
|
|
||||||
server.use(
|
|
||||||
http.get(`${API}/test-auth`, ({ request }) => {
|
|
||||||
capturedAuth = request.headers.get('Authorization')
|
|
||||||
return HttpResponse.json({
|
|
||||||
data: { data: { ok: true }, meta: { fromCache: false, cachedAt: null } },
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
await request('/api/v1/test-auth')
|
|
||||||
expect(capturedAuth).toBe('Bearer test-token')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('retries on 401 and succeeds after refresh', async () => {
|
|
||||||
setAccessToken('expired-token')
|
|
||||||
let attempts = 0
|
|
||||||
server.use(
|
|
||||||
http.get(`${API}/test-retry`, ({ request }) => {
|
|
||||||
attempts++
|
|
||||||
const auth = request.headers.get('Authorization')
|
|
||||||
if (auth === 'Bearer expired-token') {
|
|
||||||
return new HttpResponse(null, { status: 401 })
|
|
||||||
}
|
|
||||||
return HttpResponse.json({
|
|
||||||
data: { data: { ok: true }, meta: { fromCache: false, cachedAt: null } },
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
http.post(`${API}/auth/refresh`, () =>
|
|
||||||
HttpResponse.json({
|
|
||||||
data: {
|
|
||||||
data: {
|
|
||||||
user: { id: 1, email: 'user@test.com', name: null, role: 'user' },
|
|
||||||
accessToken: 'new-token',
|
|
||||||
},
|
|
||||||
meta: { fromCache: false, cachedAt: null },
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
const result = await request<{ ok: boolean }>('/api/v1/test-retry')
|
|
||||||
expect(attempts).toBe(2)
|
|
||||||
expect(result.data).toEqual({ ok: true })
|
|
||||||
expect(getAccessToken()).toBe('new-token')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('throws on persistent 401 and clears token', async () => {
|
|
||||||
setAccessToken('expired-token')
|
|
||||||
let unauthorizedCalled = false
|
|
||||||
setOnUnauthorized(() => {
|
|
||||||
unauthorizedCalled = true
|
|
||||||
})
|
|
||||||
server.use(
|
|
||||||
http.get(`${API}/test-fail`, () => new HttpResponse(null, { status: 401 })),
|
|
||||||
http.post(`${API}/auth/refresh`, () => new HttpResponse(null, { status: 401 })),
|
|
||||||
)
|
|
||||||
await expect(request('/api/v1/test-fail')).rejects.toThrow('Сессия истекла')
|
|
||||||
expect(getAccessToken()).toBeNull()
|
|
||||||
expect(unauthorizedCalled).toBe(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('throws on non-ok response with status text', async () => {
|
|
||||||
server.use(
|
|
||||||
http.get(
|
|
||||||
`${API}/test-error`,
|
|
||||||
() => new HttpResponse('Not found', { status: 404, statusText: 'Not Found' }),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
await expect(request('/api/v1/test-error')).rejects.toThrow('Ошибка API: 404')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('sends JSON body for POST requests', async () => {
|
|
||||||
let capturedBody: string | null = null
|
|
||||||
server.use(
|
|
||||||
http.post(`${API}/test-post`, async ({ request }) => {
|
|
||||||
capturedBody = await request.text()
|
|
||||||
return HttpResponse.json({
|
|
||||||
data: { data: { ok: true }, meta: { fromCache: false, cachedAt: null } },
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
await request('/api/v1/test-post', undefined, { method: 'POST', body: { foo: 'bar' } })
|
|
||||||
expect(capturedBody).toBe(JSON.stringify({ foo: 'bar' }))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('does not send auth header when skipAuth is true', async () => {
|
|
||||||
setAccessToken('test-token')
|
|
||||||
let capturedAuth: string | null = null
|
|
||||||
server.use(
|
|
||||||
http.get(`${API}/test-skip`, ({ request }) => {
|
|
||||||
capturedAuth = request.headers.get('Authorization')
|
|
||||||
return HttpResponse.json({
|
|
||||||
data: { data: { ok: true }, meta: { fromCache: false, cachedAt: null } },
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
await request('/api/v1/test-skip', undefined, { skipAuth: true })
|
|
||||||
expect(capturedAuth).toBeNull()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('sets query params correctly', async () => {
|
|
||||||
let capturedUrl = ''
|
|
||||||
server.use(
|
|
||||||
http.get(`${API}/test-params`, ({ request }) => {
|
|
||||||
capturedUrl = request.url
|
|
||||||
return HttpResponse.json({
|
|
||||||
data: { data: { ok: true }, meta: { fromCache: false, cachedAt: null } },
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
await request('/api/v1/test-params', { q: 'sber', type: 'share' })
|
|
||||||
expect(capturedUrl).toContain('q=sber')
|
|
||||||
expect(capturedUrl).toContain('type=share')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,36 +1,62 @@
|
|||||||
|
import type { components } from './types'
|
||||||
|
|
||||||
export { configureKyAuth, getHealth, request } from './kyClient'
|
export { configureKyAuth, getHealth, request } from './kyClient'
|
||||||
export type {
|
|
||||||
AnalyticsResponse,
|
export type ApiResponseMeta = components['schemas']['ApiResponseMeta']
|
||||||
ApiEnvelope,
|
|
||||||
ApiResponseMeta,
|
export interface ApiEnvelope<T> {
|
||||||
AuthResponse,
|
data: T
|
||||||
BondHistoryItem,
|
meta: ApiResponseMeta
|
||||||
BondMarketData,
|
}
|
||||||
BondResponse,
|
|
||||||
BrokerAccount,
|
// Auth
|
||||||
BrokerEventItem,
|
export type AuthResponse = components['schemas']['AuthTokenDataDto']
|
||||||
BrokerEventsData,
|
export type UserResponse = components['schemas']['AuthUserDto']
|
||||||
BrokerEventsSummary,
|
|
||||||
BrokerMoney,
|
// Shares
|
||||||
BrokerOperation,
|
export type ShareResponse = components['schemas']['ShareResponseDto']
|
||||||
BrokerOperationCategory,
|
export type StockMarketData = components['schemas']['StockMarketDataDto']
|
||||||
BrokerOperationsPage,
|
export type DividendItem = components['schemas']['DividendItemDto']
|
||||||
BrokerPortfolio,
|
export type ShareHistoryItem = components['schemas']['HistoryItemDto']
|
||||||
BrokerPosition,
|
|
||||||
BrokerPositionsPage,
|
// Bonds
|
||||||
CandleItem,
|
export type BondResponse = components['schemas']['BondResponseDto']
|
||||||
DividendItem,
|
export type BondMarketData = components['schemas']['BondMarketDataDto']
|
||||||
HealthResponse,
|
export type BondHistoryItem = components['schemas']['BondHistoryItemDto']
|
||||||
Portfolio,
|
|
||||||
PortfolioDetail,
|
// Candles
|
||||||
PortfolioSummary,
|
export type CandleItem = components['schemas']['CandleItemDto']
|
||||||
Position,
|
|
||||||
PositionWithPrice,
|
// Search
|
||||||
ScreenerItem,
|
export type SearchResultItem = components['schemas']['SearchResultItemDto']
|
||||||
ScreenerResult,
|
|
||||||
SearchResultItem,
|
// Screener
|
||||||
ShareHistoryItem,
|
export type ScreenerResult = components['schemas']['ScreenerResultDto']
|
||||||
ShareResponse,
|
export type ScreenerItem = components['schemas']['ScreenerItemDto']
|
||||||
StockMarketData,
|
|
||||||
UserResponse,
|
// Portfolio
|
||||||
} from './responses'
|
export type Portfolio = components['schemas']['PortfolioListResponseDto']
|
||||||
|
export type PortfolioDetail = components['schemas']['PortfolioDetailResponseDto']
|
||||||
|
export type Position = components['schemas']['PositionResponseDto']
|
||||||
|
export type PositionWithPrice = components['schemas']['PositionWithPriceDto']
|
||||||
|
export type PortfolioSummary = components['schemas']['PortfolioSummaryDto']
|
||||||
|
export type AnalyticsResponse = components['schemas']['AnalyticsResponseDto']
|
||||||
|
|
||||||
|
// Health
|
||||||
|
export type HealthResponse = components['schemas']['HealthResponseDto']
|
||||||
|
|
||||||
|
// Broker
|
||||||
|
export type BrokerAccount = components['schemas']['BrokerAccountResponseDto']
|
||||||
|
export type BrokerPortfolio = components['schemas']['BrokerPortfolioResponseDto']
|
||||||
|
export type BrokerMoney = components['schemas']['BrokerMoneyDto']
|
||||||
|
export type BrokerPosition = components['schemas']['BrokerPositionResponseDto']
|
||||||
|
export type BrokerOperation = components['schemas']['BrokerOperationResponseDto']
|
||||||
|
export type BrokerOperationsPage = components['schemas']['BrokerOperationsPageResponseDto']
|
||||||
|
export type BrokerPositionsPage = components['schemas']['BrokerPositionsPageResponseDto']
|
||||||
|
export type BrokerOperationCategory =
|
||||||
|
components['schemas']['BrokerOperationResponseDto']['category']
|
||||||
|
|
||||||
|
// Broker events
|
||||||
|
export type BrokerEventItem = components['schemas']['BrokerEventItemDto']
|
||||||
|
export type BrokerEventsData = components['schemas']['BrokerEventsDataDto']
|
||||||
|
export type BrokerEventsSummary = components['schemas']['BrokerEventsSummaryDto']
|
||||||
|
|||||||
@ -1,62 +0,0 @@
|
|||||||
import type { components } from './types'
|
|
||||||
|
|
||||||
// Re-exported from openapi-typescript codegen with friendly names
|
|
||||||
|
|
||||||
export type ApiResponseMeta = components['schemas']['ApiResponseMeta']
|
|
||||||
|
|
||||||
export interface ApiEnvelope<T> {
|
|
||||||
data: T
|
|
||||||
meta: ApiResponseMeta
|
|
||||||
}
|
|
||||||
|
|
||||||
// Auth
|
|
||||||
export type AuthResponse = components['schemas']['AuthTokenDataDto']
|
|
||||||
export type UserResponse = components['schemas']['AuthUserDto']
|
|
||||||
|
|
||||||
// Shares
|
|
||||||
export type ShareResponse = components['schemas']['ShareResponseDto']
|
|
||||||
export type StockMarketData = components['schemas']['StockMarketDataDto']
|
|
||||||
export type DividendItem = components['schemas']['DividendItemDto']
|
|
||||||
export type ShareHistoryItem = components['schemas']['HistoryItemDto']
|
|
||||||
|
|
||||||
// Bonds
|
|
||||||
export type BondResponse = components['schemas']['BondResponseDto']
|
|
||||||
export type BondMarketData = components['schemas']['BondMarketDataDto']
|
|
||||||
export type BondHistoryItem = components['schemas']['BondHistoryItemDto']
|
|
||||||
|
|
||||||
// Candles
|
|
||||||
export type CandleItem = components['schemas']['CandleItemDto']
|
|
||||||
|
|
||||||
// Search
|
|
||||||
export type SearchResultItem = components['schemas']['SearchResultItemDto']
|
|
||||||
|
|
||||||
// Screener
|
|
||||||
export type ScreenerResult = components['schemas']['ScreenerResultDto']
|
|
||||||
export type ScreenerItem = components['schemas']['ScreenerItemDto']
|
|
||||||
|
|
||||||
// Portfolio
|
|
||||||
export type Portfolio = components['schemas']['PortfolioListResponseDto']
|
|
||||||
export type PortfolioDetail = components['schemas']['PortfolioDetailResponseDto']
|
|
||||||
export type Position = components['schemas']['PositionResponseDto']
|
|
||||||
export type PositionWithPrice = components['schemas']['PositionWithPriceDto']
|
|
||||||
export type PortfolioSummary = components['schemas']['PortfolioSummaryDto']
|
|
||||||
export type AnalyticsResponse = components['schemas']['AnalyticsResponseDto']
|
|
||||||
|
|
||||||
// Health
|
|
||||||
export type HealthResponse = components['schemas']['HealthResponseDto']
|
|
||||||
|
|
||||||
// Broker — some nullable fields still have Record<string, never> from backend
|
|
||||||
export type BrokerAccount = components['schemas']['BrokerAccountResponseDto']
|
|
||||||
export type BrokerPortfolio = components['schemas']['BrokerPortfolioResponseDto']
|
|
||||||
export type BrokerMoney = components['schemas']['BrokerMoneyDto']
|
|
||||||
export type BrokerPosition = components['schemas']['BrokerPositionResponseDto']
|
|
||||||
export type BrokerOperation = components['schemas']['BrokerOperationResponseDto']
|
|
||||||
export type BrokerOperationsPage = components['schemas']['BrokerOperationsPageResponseDto']
|
|
||||||
export type BrokerPositionsPage = components['schemas']['BrokerPositionsPageResponseDto']
|
|
||||||
export type BrokerOperationCategory =
|
|
||||||
components['schemas']['BrokerOperationResponseDto']['category']
|
|
||||||
|
|
||||||
// Broker events
|
|
||||||
export type BrokerEventItem = components['schemas']['BrokerEventItemDto']
|
|
||||||
export type BrokerEventsData = components['schemas']['BrokerEventsDataDto']
|
|
||||||
export type BrokerEventsSummary = components['schemas']['BrokerEventsSummaryDto']
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import type { BrokerMoney } from '@/shared/api/responses'
|
import type { BrokerMoney } from '@/shared/api'
|
||||||
|
|
||||||
export function formatBrokerCurrencyValue(currency: string, value: number): string {
|
export function formatBrokerCurrencyValue(currency: string, value: number): string {
|
||||||
return new Intl.NumberFormat('ru-RU', {
|
return new Intl.NumberFormat('ru-RU', {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { createContext } from 'react'
|
import { createContext } from 'react'
|
||||||
import type { UserResponse } from '@/shared/api/responses'
|
import type { UserResponse } from '@/shared/api'
|
||||||
|
|
||||||
export interface SessionContextValue {
|
export interface SessionContextValue {
|
||||||
user: UserResponse | null
|
user: UserResponse | null
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import type {
|
|||||||
ShareResponse,
|
ShareResponse,
|
||||||
StockMarketData,
|
StockMarketData,
|
||||||
UserResponse,
|
UserResponse,
|
||||||
} from '@/shared/api/responses'
|
} from '@/shared/api'
|
||||||
|
|
||||||
export function createMockMarketData(overrides: Partial<StockMarketData> = {}): StockMarketData {
|
export function createMockMarketData(overrides: Partial<StockMarketData> = {}): StockMarketData {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -1,10 +1,5 @@
|
|||||||
import { HttpResponse, http } from 'msw'
|
import { HttpResponse, http } from 'msw'
|
||||||
import type {
|
import type { BondResponse, CandleItem, SearchResultItem, ShareResponse } from '@/shared/api'
|
||||||
BondResponse,
|
|
||||||
CandleItem,
|
|
||||||
SearchResultItem,
|
|
||||||
ShareResponse,
|
|
||||||
} from '@/shared/api/responses'
|
|
||||||
|
|
||||||
const API = '/api/v1'
|
const API = '/api/v1'
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { BondResponse } from '@/shared/api/responses'
|
import type { BondResponse } from '@/shared/api'
|
||||||
|
|
||||||
interface BondDetailsProps {
|
interface BondDetailsProps {
|
||||||
bond: BondResponse
|
bond: BondResponse
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Link } from '@tanstack/react-router'
|
import { Link } from '@tanstack/react-router'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import type { PositionWithPrice } from '@/shared/api/responses'
|
import type { PositionWithPrice } from '@/shared/api'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
position: PositionWithPrice
|
position: PositionWithPrice
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { PositionWithPrice } from '@/shared/api/responses'
|
import type { PositionWithPrice } from '@/shared/api'
|
||||||
import { BondPositionRow } from './BondPositionRow'
|
import { BondPositionRow } from './BondPositionRow'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { Alert, Button, Heading, Skeleton, Text } from '@moex-vibe/design-system
|
|||||||
import { Box } from '@mui/material'
|
import { Box } from '@mui/material'
|
||||||
import { Link } from '@tanstack/react-router'
|
import { Link } from '@tanstack/react-router'
|
||||||
import { buildBrokerAllocation } from '@/entities/broker-position'
|
import { buildBrokerAllocation } from '@/entities/broker-position'
|
||||||
import type { BrokerAccount, BrokerPortfolio } from '@/shared/api/responses'
|
import type { BrokerAccount, BrokerPortfolio } from '@/shared/api'
|
||||||
import {
|
import {
|
||||||
formatBrokerDate,
|
formatBrokerDate,
|
||||||
formatBrokerMoney,
|
formatBrokerMoney,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Text } from '@moex-vibe/design-system'
|
import { Text } from '@moex-vibe/design-system'
|
||||||
import { Box } from '@mui/material'
|
import { Box } from '@mui/material'
|
||||||
import { buildBrokerAllocation } from '@/entities/broker-position'
|
import { buildBrokerAllocation } from '@/entities/broker-position'
|
||||||
import type { BrokerPortfolio } from '@/shared/api/responses'
|
import type { BrokerPortfolio } from '@/shared/api'
|
||||||
import { formatBrokerCurrencyValue } from '@/shared/lib/formatters'
|
import { formatBrokerCurrencyValue } from '@/shared/lib/formatters'
|
||||||
|
|
||||||
const RADIUS = 44
|
const RADIUS = 44
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import {
|
|||||||
getBrokerOperationTypeLabel,
|
getBrokerOperationTypeLabel,
|
||||||
} from '@/entities/broker-operation'
|
} from '@/entities/broker-operation'
|
||||||
import { getBrokerInstrumentPath } from '@/entities/broker-position'
|
import { getBrokerInstrumentPath } from '@/entities/broker-position'
|
||||||
import type { BrokerOperation, BrokerOperationsPage } from '@/shared/api/responses'
|
import type { BrokerOperation, BrokerOperationsPage } from '@/shared/api'
|
||||||
import { formatBrokerSignedMoney } from '@/shared/lib/formatters'
|
import { formatBrokerSignedMoney } from '@/shared/lib/formatters'
|
||||||
import { TableSkeleton } from '@/shared/ui/TableSkeleton'
|
import { TableSkeleton } from '@/shared/ui/TableSkeleton'
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Text } from '@moex-vibe/design-system'
|
import { Text } from '@moex-vibe/design-system'
|
||||||
import { Box } from '@mui/material'
|
import { Box } from '@mui/material'
|
||||||
import { Link } from '@tanstack/react-router'
|
import { Link } from '@tanstack/react-router'
|
||||||
import type { BrokerMoney, BrokerPortfolio } from '@/shared/api/responses'
|
import type { BrokerMoney, BrokerPortfolio } from '@/shared/api'
|
||||||
import { formatBrokerMoney, pluralize } from '@/shared/lib/formatters'
|
import { formatBrokerMoney, pluralize } from '@/shared/lib/formatters'
|
||||||
|
|
||||||
function allocationPercent(value: BrokerMoney | null, total: BrokerMoney | null) {
|
function allocationPercent(value: BrokerMoney | null, total: BrokerMoney | null) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Text } from '@moex-vibe/design-system'
|
import { Text } from '@moex-vibe/design-system'
|
||||||
import { Box } from '@mui/material'
|
import { Box } from '@mui/material'
|
||||||
import type { BrokerPortfolio } from '@/shared/api/responses'
|
import type { BrokerPortfolio } from '@/shared/api'
|
||||||
import {
|
import {
|
||||||
formatBrokerMoney as formatMoney,
|
formatBrokerMoney as formatMoney,
|
||||||
formatBrokerPercent as formatPercent,
|
formatBrokerPercent as formatPercent,
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Button, Heading, Skeleton, Text } from '@moex-vibe/design-system'
|
import { Button, Heading, Skeleton, Text } from '@moex-vibe/design-system'
|
||||||
import { Box } from '@mui/material'
|
import { Box } from '@mui/material'
|
||||||
import type { BrokerPositionsPage as BrokerPositionsPageData } from '@/shared/api/responses'
|
import type { BrokerPositionsPage as BrokerPositionsPageData } from '@/shared/api'
|
||||||
import { formatBrokerMoney as formatMoney } from '@/shared/lib/formatters'
|
import { formatBrokerMoney as formatMoney } from '@/shared/lib/formatters'
|
||||||
import { PositionTicker } from './PositionTicker'
|
import { PositionTicker } from './PositionTicker'
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Box } from '@mui/material'
|
import { Box } from '@mui/material'
|
||||||
import { Link } from '@tanstack/react-router'
|
import { Link } from '@tanstack/react-router'
|
||||||
import { getBrokerInstrumentPath } from '@/entities/broker-position'
|
import { getBrokerInstrumentPath } from '@/entities/broker-position'
|
||||||
import type { BrokerPosition } from '@/shared/api/responses'
|
import type { BrokerPosition } from '@/shared/api'
|
||||||
|
|
||||||
export function PositionTicker({ position }: { position: BrokerPosition }) {
|
export function PositionTicker({ position }: { position: BrokerPosition }) {
|
||||||
const label = position.ticker || position.figi || '-'
|
const label = position.ticker || position.figi || '-'
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { DividendItem } from '@/shared/api/responses'
|
import type { DividendItem } from '@/shared/api'
|
||||||
|
|
||||||
interface DividendsTableProps {
|
interface DividendsTableProps {
|
||||||
dividends: DividendItem[]
|
dividends: DividendItem[]
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { PortfolioSummary } from '@/shared/api/responses'
|
import type { PortfolioSummary } from '@/shared/api'
|
||||||
|
|
||||||
export function AnalyticsSummary({ summary }: { summary: PortfolioSummary }) {
|
export function AnalyticsSummary({ summary }: { summary: PortfolioSummary }) {
|
||||||
const formatRub = (val: number | null) =>
|
const formatRub = (val: number | null) =>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Link } from '@tanstack/react-router'
|
import { Link } from '@tanstack/react-router'
|
||||||
import type { Portfolio } from '@/shared/api/responses'
|
import type { Portfolio } from '@/shared/api'
|
||||||
import { pluralize } from '@/shared/lib/formatters'
|
import { pluralize } from '@/shared/lib/formatters'
|
||||||
|
|
||||||
export function PortfolioCard({ portfolio }: { portfolio: Portfolio }) {
|
export function PortfolioCard({ portfolio }: { portfolio: Portfolio }) {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import type { Portfolio } from '@/shared/api/responses'
|
import type { Portfolio } from '@/shared/api'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
initial?: Portfolio
|
initial?: Portfolio
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { PositionWithPrice } from '@/shared/api/responses'
|
import type { PositionWithPrice } from '@/shared/api'
|
||||||
|
|
||||||
interface AllocationChartProps {
|
interface AllocationChartProps {
|
||||||
positions: PositionWithPrice[]
|
positions: PositionWithPrice[]
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { PortfolioDetail } from '@/shared/api/responses'
|
import type { PortfolioDetail } from '@/shared/api'
|
||||||
import { AllocationChart } from './AllocationChart'
|
import { AllocationChart } from './AllocationChart'
|
||||||
|
|
||||||
export function PortfolioSummary({ portfolio }: { portfolio: PortfolioDetail }) {
|
export function PortfolioSummary({ portfolio }: { portfolio: PortfolioDetail }) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Link } from '@tanstack/react-router'
|
import { Link } from '@tanstack/react-router'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import type { PositionWithPrice } from '@/shared/api/responses'
|
import type { PositionWithPrice } from '@/shared/api'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
position: PositionWithPrice
|
position: PositionWithPrice
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { PositionWithPrice } from '@/shared/api/responses'
|
import type { PositionWithPrice } from '@/shared/api'
|
||||||
import { SharePositionRow } from './SharePositionRow'
|
import { SharePositionRow } from './SharePositionRow'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import type { ShareResponse } from '@/shared/api/responses'
|
import type { ShareResponse } from '@/shared/api'
|
||||||
|
|
||||||
interface StockDetailsProps {
|
interface StockDetailsProps {
|
||||||
stock: ShareResponse
|
stock: ShareResponse
|
||||||
|
|||||||
@ -47,10 +47,14 @@
|
|||||||
### Phase 3 — Unify API Types
|
### Phase 3 — Unify API Types
|
||||||
*Низкий риск*
|
*Низкий риск*
|
||||||
|
|
||||||
1. Убедиться, что все entity API импортируют из `types.ts` (codegen)
|
> **Фактический подход: ре-экспорты в index.ts вместо types.ts.**
|
||||||
|
> В процессе реализации выяснилось, что direct-импорты из `types.ts` ведут к многословным `components['schemas']['XxxDto']` в 60+ файлах. Принято решение перенести friendly-name ре-экспорты из `responses.ts` в `index.ts`. Таким образом `responses.ts` удалён, а единая точка входа — `@/shared/api`.
|
||||||
|
|
||||||
|
1. Перенести friendly-name type-алиасы из `responses.ts` в `shared/api/index.ts`
|
||||||
2. Удалить `shared/api/responses.ts`
|
2. Удалить `shared/api/responses.ts`
|
||||||
3. Перенести normalizeEnvelope (ky-версию) в `shared/api/kyClient.ts`
|
3. Переключить все импорты с `@/shared/api/responses` на `@/shared/api`
|
||||||
4. Прогнать `npm run build`
|
4. Перенести normalizeEnvelope (ky-версию) в `shared/api/kyClient.ts`
|
||||||
|
5. Прогнать `npm run build`
|
||||||
|
|
||||||
### Phase 4 — MSW Browser
|
### Phase 4 — MSW Browser
|
||||||
*Низкий риск, handlers готовы*
|
*Низкий риск, handlers готовы*
|
||||||
|
|||||||
@ -21,9 +21,9 @@
|
|||||||
|
|
||||||
## Phase 2: Biome Migration
|
## Phase 2: Biome Migration
|
||||||
|
|
||||||
- [ ] Research: проверить Biome plugin system на поддержку FSD/import-no-restricted-paths
|
- [x] Research: проверить Biome plugin system на поддержку FSD/import-no-restricted-paths — не поддерживается, ESLint оставлен для FSD
|
||||||
- [x] Установить `@biomejs/biome` (devDependency)
|
- [x] Установить `@biomejs/biome` (devDependency)
|
||||||
- [ ] Запустить `npx @biomejs/biome migrate eslint --write`
|
- [x] Запустить `npx @biomejs/biome migrate eslint --write` — 0 правил мигрировано, FSD-правила несовместимы, конфиг откачен
|
||||||
- [x] Создать и настроить `biome.json` под проект
|
- [x] Создать и настроить `biome.json` под проект
|
||||||
- [x] ESLint оставлен только для FSD-правил (`.eslintrc.cjs`)
|
- [x] ESLint оставлен только для FSD-правил (`.eslintrc.cjs`)
|
||||||
- [x] Удалить зависимость prettier (ESLint core, @typescript-eslint/*, eslint-plugin-* оставлены для FSD-правил)
|
- [x] Удалить зависимость prettier (ESLint core, @typescript-eslint/*, eslint-plugin-* оставлены для FSD-правил)
|
||||||
@ -37,11 +37,11 @@
|
|||||||
|
|
||||||
## Phase 3: Unify API Types
|
## Phase 3: Unify API Types
|
||||||
|
|
||||||
- [ ] Аудит импортов: entity API используют `types.ts` (codegen) или `responses.ts`?
|
- [x] Аудит импортов: entity API используют `types.ts` (codegen) или `responses.ts`?
|
||||||
- [ ] Переключить все импорты с `responses.ts` на `types.ts` (если типы есть в codegen)
|
- [x] Переключить все импорты с `responses.ts` на `@/shared/api` (типы перенесены в `index.ts` вместо рукописного файла)
|
||||||
- [ ] Удалить `shared/api/responses.ts` (после переключения)
|
- [x] Удалить `shared/api/responses.ts` (после переключения)
|
||||||
- [x] normalizeEnvelope перенесён в `shared/api/kyClient.ts`
|
- [x] normalizeEnvelope перенесён в `shared/api/kyClient.ts`
|
||||||
- [ ] `npm run build` — сборка проходит
|
- [x] `npm run build` — сборка проходит
|
||||||
|
|
||||||
## Phase 4: MSW Browser
|
## Phase 4: MSW Browser
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user