fix(web): keep cached library rows when a refresh fails
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5c837a6679
commit
4166ecb8f5
2 files changed
+30
-2
No files matched your search
@@ -0,0 +1,22 @@
|
|||||||
|
import { friendlyError } from "../format.ts";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A failed load. With cached data on screen (`stale`), a small inline banner sits above it so the user
|
||||||
|
* keeps what they had; with nothing cached, it is the whole state.
|
||||||
|
*/
|
||||||
|
export function LoadError({ error, retry, stale, className }: { error: unknown; retry: () => void; stale?: boolean; className?: string }) {
|
||||||
|
if (stale) {
|
||||||
|
return (
|
||||||
|
<div className={`stale-banner${className ? ` ${className}` : ""}`} role="alert">
|
||||||
|
<p>Can't refresh right now. {friendlyError(error)}</p>
|
||||||
|
<button type="button" className="btn ghost small" onClick={retry}>Try again</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={`load-error${className ? ` ${className}` : ""}`} role="alert">
|
||||||
|
<p>{friendlyError(error)}</p>
|
||||||
|
<button type="button" className="btn small" onClick={retry}>Try again</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ import { useInfiniteQuery, useMutation, useQueryClient } from "@tanstack/react-q
|
|||||||
import { useRef, useState, type FormEvent } from "react";
|
import { useRef, useState, type FormEvent } from "react";
|
||||||
import { Link, useLocation } from "wouter";
|
import { Link, useLocation } from "wouter";
|
||||||
import { api, type EntryView } from "../api.ts";
|
import { api, type EntryView } from "../api.ts";
|
||||||
|
import { LoadError } from "../components/LoadError.tsx";
|
||||||
import { Cover } from "../components/Cover.tsx";
|
import { Cover } from "../components/Cover.tsx";
|
||||||
import { EntryDetail } from "../components/EntryDetail.tsx";
|
import { EntryDetail } from "../components/EntryDetail.tsx";
|
||||||
import { Icon } from "../components/Icon.tsx";
|
import { Icon } from "../components/Icon.tsx";
|
||||||
@@ -165,12 +166,12 @@ function ListPane({ selectedId }: { selectedId: string | null }) {
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
) : entries.error ? (
|
) : entries.error && !entries.data ? (
|
||||||
<div className="list-note" role="alert">
|
<div className="list-note" role="alert">
|
||||||
<p>{friendlyError(entries.error)}</p>
|
<p>{friendlyError(entries.error)}</p>
|
||||||
<button className="btn small" onClick={() => void entries.refetch()}>Try again</button>
|
<button className="btn small" onClick={() => void entries.refetch()}>Try again</button>
|
||||||
</div>
|
</div>
|
||||||
) : items.length === 0 ? (
|
) : items.length === 0 && !entries.error ? (
|
||||||
<div className="list-note">
|
<div className="list-note">
|
||||||
{filtering ? (
|
{filtering ? (
|
||||||
<>
|
<>
|
||||||
@@ -186,6 +187,11 @@ function ListPane({ selectedId }: { selectedId: string | null }) {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<ul className="list">
|
<ul className="list">
|
||||||
|
{entries.error && (
|
||||||
|
<li>
|
||||||
|
<LoadError error={entries.error} retry={() => void entries.refetch()} stale className="in-list" />
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
{items.map((e) => (
|
{items.map((e) => (
|
||||||
<li key={e.id}>
|
<li key={e.id}>
|
||||||
<Row entry={e} selected={e.id === selectedId} flagged={flagged.has(e.id)} scale={scale} />
|
<Row entry={e} selected={e.id === selectedId} flagged={flagged.has(e.id)} scale={scale} />
|
||||||
|
|||||||
Reference in new issue
Block a user