33 lines
1.7 KiB
TypeScript
33 lines
1.7 KiB
TypeScript
import { useRef, useState } from 'react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { normalizeAppError } from '@/lib/error-model';
|
|
import { codingRecovery, type CodingRecoveryAction } from '../../../shared/coding-recovery';
|
|
|
|
export function CodingRecoveryCard({ code, work, onRecovery }: {
|
|
code?: string;
|
|
work: boolean;
|
|
onRecovery(action: CodingRecoveryAction, work: boolean): Promise<void> | void;
|
|
}) {
|
|
const [busy, setBusy] = useState(false);
|
|
const [retryCode, setRetryCode] = useState<string>();
|
|
const [retryFailed, setRetryFailed] = useState(false);
|
|
const flight = useRef(false);
|
|
const recovery = codingRecovery(retryCode ?? code, work);
|
|
return <div role="status" className="rounded-xl border border-brand/15 bg-brand/5 p-4 text-sm" data-testid="coding-recovery-card">
|
|
<p className="font-medium">{work ? '作品还没有打开' : '这一步需要再接着做'}</p>
|
|
<p className="mt-1 text-muted-foreground">{retryFailed ? '暂时还没连接上。可以稍后再试,已输入的内容会保留。' : recovery.message}</p>
|
|
<Button className="mt-3" size="sm" variant="outline" disabled={busy} onClick={async () => {
|
|
if (flight.current) return;
|
|
flight.current = true;
|
|
setBusy(true);
|
|
setRetryFailed(false);
|
|
try { await onRecovery(recovery.action, work); }
|
|
catch (error) {
|
|
const failure = normalizeAppError(error);
|
|
setRetryCode(typeof failure.details?.backendCode === 'string' ? failure.details.backendCode : failure.code);
|
|
setRetryFailed(failure.code !== 'AUTH_INVALID');
|
|
} finally { flight.current = false; setBusy(false); }
|
|
}}>{busy ? '正在接着处理…' : recovery.label}</Button>
|
|
</div>;
|
|
}
|