Skip to content

Commit a3ad894

Browse files
committed
refactor(admin): use @mx-space/editor stock types; surface transient fetch errors
Drop local Stock node type defs; re-route through @mx-space/editor 0.2.0. Stock fetch failures now distinguish 404 (Symbol not found) from transient errors (Failed to load + retry button), so a network blip no longer claims the symbol is unavailable.
1 parent 56903b9 commit a3ad894

6 files changed

Lines changed: 77 additions & 38 deletions

File tree

apps/admin/src/vendor/rich-editor/extensions/stock/StockKLineView.tsx

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
UP_STROKE,
1616
} from './_shared'
1717
import type { Bar, StockKLineInterval, StockMeta } from './types'
18-
import { useStockBars } from './use-stock-data'
18+
import { isStockNotFound, useStockBars } from './use-stock-data'
1919

2020
type Props = {
2121
symbol: string
@@ -318,10 +318,12 @@ function FallbackChrome({
318318
symbol,
319319
rangeLabel,
320320
message,
321+
onRetry,
321322
}: {
322323
symbol: string
323324
rangeLabel: string
324325
message: string
326+
onRetry?: () => void
325327
}) {
326328
return (
327329
<PaperCardChrome>
@@ -331,8 +333,17 @@ function FallbackChrome({
331333
{rangeLabel}
332334
</span>
333335
</div>
334-
<div className="text-fg-muted flex h-[200px] w-full items-center justify-center text-[12px] italic">
335-
{message}
336+
<div className="text-fg-muted flex h-[200px] w-full flex-col items-center justify-center gap-3 text-[12px] italic">
337+
<span>{message}</span>
338+
{onRetry ? (
339+
<button
340+
type="button"
341+
onClick={onRetry}
342+
className="text-fg-muted hover:text-fg border-border hover:border-border-strong rounded-sm border px-2 py-1 text-[11px] not-italic transition-colors"
343+
>
344+
Retry
345+
</button>
346+
) : null}
336347
</div>
337348
</PaperCardChrome>
338349
)
@@ -503,7 +514,7 @@ function KLineCard({
503514
export function StockKLineView({ symbol, range, ema }: Props) {
504515
const { locale } = useI18n()
505516
const rangeLabel = formatRange(range.from, range.to, locale)
506-
const { data, isLoading, isError } = useStockBars({
517+
const { data, isLoading, isError, error, refetch } = useStockBars({
507518
symbol,
508519
from: range.from,
509520
to: range.to,
@@ -520,11 +531,13 @@ export function StockKLineView({ symbol, range, ema }: Props) {
520531
)
521532
}
522533
if (isError || !data) {
534+
const notFound = isStockNotFound(error)
523535
return (
524536
<FallbackChrome
525-
message="Quote unavailable"
537+
message={notFound ? 'Symbol not found' : 'Failed to load chart'}
526538
rangeLabel={rangeLabel}
527539
symbol={symbol}
540+
onRetry={notFound ? undefined : () => void refetch()}
528541
/>
529542
)
530543
}

apps/admin/src/vendor/rich-editor/extensions/stock/StockNode.ts

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import type { CommandItemConfig } from '@haklex/rich-editor/commands'
22
import { createRendererDecoration } from '@haklex/rich-editor/static'
3+
import type {
4+
StockKLineRange,
5+
StockNodePayload,
6+
StockVariant,
7+
} from '@mx-space/editor'
38
import type {
49
EditorConfig,
510
LexicalCommand,
@@ -18,26 +23,11 @@ import {
1823
import { LineChart } from 'lucide-react'
1924
import { createElement, type ReactElement } from 'react'
2025

21-
import {
22-
STOCK_NODE_KEY,
23-
type StockKLineRange,
24-
type StockSlotProps,
25-
type StockVariant,
26-
} from './stock-augment'
26+
import { STOCK_NODE_KEY, type StockSlotProps } from './stock-augment'
2727
import { openStockDialog } from './stock-plugin-bridge'
2828
import { StockBlockConnected } from './StockBlockConnected'
2929

30-
export type StockNodePayload =
31-
| {
32-
variant: 'snapshot'
33-
symbol: string
34-
}
35-
| {
36-
variant: 'kline'
37-
symbol: string
38-
range: StockKLineRange
39-
ema?: [number, number] | false
40-
}
30+
export type { StockNodePayload }
4131

4232
export const INSERT_STOCK_COMMAND: LexicalCommand<StockNodePayload> =
4333
createCommand('INSERT_STOCK_COMMAND')

apps/admin/src/vendor/rich-editor/extensions/stock/StockSnapshotView.tsx

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
UP_STROKE,
1212
} from './_shared'
1313
import type { Quote, SparklinePoint, StockMeta } from './types'
14-
import { useStockQuote } from './use-stock-data'
14+
import { isStockNotFound, useStockQuote } from './use-stock-data'
1515

1616
const SPARK_W = 220
1717
const SPARK_H = 56
@@ -51,7 +51,15 @@ function buildSparkPath(points: SparklinePoint[]): string {
5151
return segs.join(' ')
5252
}
5353

54-
function Unavailable({ symbol, message }: { symbol: string; message: string }) {
54+
function Unavailable({
55+
symbol,
56+
message,
57+
onRetry,
58+
}: {
59+
symbol: string
60+
message: string
61+
onRetry?: () => void
62+
}) {
5563
return (
5664
<PaperCardChrome>
5765
<header className="mb-4">
@@ -63,6 +71,15 @@ function Unavailable({ symbol, message }: { symbol: string; message: string }) {
6371
</div>
6472
</header>
6573
<p className="text-fg-muted text-[13px]">{message}</p>
74+
{onRetry ? (
75+
<button
76+
type="button"
77+
onClick={onRetry}
78+
className="text-fg-muted hover:text-fg border-border hover:border-border-strong mt-3 rounded-sm border px-2 py-1 text-[11px] transition-colors"
79+
>
80+
Retry
81+
</button>
82+
) : null}
6683
</PaperCardChrome>
6784
)
6885
}
@@ -144,13 +161,20 @@ function SnapshotCard({ quote, locale }: { quote: Quote; locale: string }) {
144161

145162
export function StockSnapshotView({ symbol }: { symbol: string }) {
146163
const { locale } = useI18n()
147-
const { data, isLoading, isError } = useStockQuote(symbol)
164+
const { data, isLoading, isError, error, refetch } = useStockQuote(symbol)
148165

149166
if (isLoading) {
150167
return <Unavailable message="Loading quote…" symbol={symbol} />
151168
}
152169
if (isError || !data) {
153-
return <Unavailable message="Quote unavailable" symbol={symbol} />
170+
const notFound = isStockNotFound(error)
171+
return (
172+
<Unavailable
173+
message={notFound ? 'Symbol not found' : 'Failed to load quote'}
174+
symbol={symbol}
175+
onRetry={notFound ? undefined : () => void refetch()}
176+
/>
177+
)
154178
}
155179
return <SnapshotCard locale={locale} quote={data} />
156180
}

apps/admin/src/vendor/rich-editor/extensions/stock/stock-augment.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,11 @@
11
import type {} from '@haklex/rich-editor'
2+
import type { StockKLineRange, StockVariant } from '@mx-space/editor'
23
import type { ComponentType } from 'react'
34

4-
import type { StockKLineInterval } from './types'
5+
export { type StockKLineRange, type StockVariant } from '@mx-space/editor'
56

67
export const STOCK_NODE_KEY = 'Stock' as const
78

8-
export type StockVariant = 'snapshot' | 'kline'
9-
10-
export interface StockKLineRange {
11-
from: string
12-
interval: StockKLineInterval
13-
to: string
14-
}
15-
169
export interface StockSlotProps {
1710
ema?: [number, number] | false
1811
nodeKey?: string

apps/admin/src/vendor/rich-editor/extensions/stock/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export type StockKLineInterval = '5m' | '15m' | '1h' | '1d'
1+
export type { StockKLineInterval } from '@mx-space/editor'
22

33
export type MarketState = 'pre' | 'regular' | 'post' | 'closed'
44

apps/admin/src/vendor/rich-editor/extensions/stock/use-stock-data.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,30 @@ function normalizeHost(url: string): string {
88
return url.replace(/\/$/, '')
99
}
1010

11+
export class StockFetchError extends Error {
12+
readonly status: number
13+
constructor(message: string, status: number) {
14+
super(message)
15+
this.name = 'StockFetchError'
16+
this.status = status
17+
}
18+
}
19+
20+
export function isStockNotFound(error: unknown): boolean {
21+
return error instanceof StockFetchError && error.status === 404
22+
}
23+
1124
async function fetchQuote(symbol: string): Promise<Quote> {
1225
const params = new URLSearchParams({ symbol })
1326
const res = await fetch(
1427
`${normalizeHost(API_URL)}/serverless/built-in/stock_quote?${params.toString()}`,
1528
{ headers: { Accept: 'application/json' } },
1629
)
1730
if (!res.ok) {
18-
throw new Error(`Failed to fetch quote (${res.status})`)
31+
throw new StockFetchError(
32+
`Failed to fetch quote (${res.status})`,
33+
res.status,
34+
)
1935
}
2036
return (await res.json()) as Quote
2137
}
@@ -37,7 +53,10 @@ async function fetchBars(args: {
3753
{ headers: { Accept: 'application/json' } },
3854
)
3955
if (!res.ok) {
40-
throw new Error(`Failed to fetch bars (${res.status})`)
56+
throw new StockFetchError(
57+
`Failed to fetch bars (${res.status})`,
58+
res.status,
59+
)
4160
}
4261
return (await res.json()) as BarsResult
4362
}

0 commit comments

Comments
 (0)