fix: null guard on API array responses to prevent crash
This commit is contained in:
+5
-2
@@ -146,9 +146,12 @@ async function request<T>(method: string, path: string, body?: unknown): Promise
|
||||
return undefined as T;
|
||||
}
|
||||
const text = await res.text();
|
||||
const data = text ? JSON.parse(text) : {};
|
||||
if (!text) {
|
||||
throw new ApiError(res.status, "empty response");
|
||||
}
|
||||
const data = JSON.parse(text);
|
||||
if (!res.ok) {
|
||||
throw new ApiError(res.status, data.error || res.statusText);
|
||||
throw new ApiError(res.status, (data && data.error) || res.statusText);
|
||||
}
|
||||
return data as T;
|
||||
}
|
||||
|
||||
@@ -47,9 +47,9 @@ export default function FileBrowserModal({ initialPath, onSelect, onClose }: Pro
|
||||
useEffect(() => {
|
||||
api.fileCapabilities().then(setCapabilities).catch(() => {});
|
||||
api.fileRoots().then(r => {
|
||||
setRoots(r.roots);
|
||||
if (!initialPath && r.roots.length > 0) {
|
||||
setCurrentPath(r.roots[0]);
|
||||
setRoots(r.roots ?? []);
|
||||
if (!initialPath && (r.roots ?? []).length > 0) {
|
||||
setCurrentPath(r.roots![0]);
|
||||
}
|
||||
}).catch(() => {});
|
||||
}, []);
|
||||
@@ -63,10 +63,11 @@ export default function FileBrowserModal({ initialPath, onSelect, onClose }: Pro
|
||||
setError(null);
|
||||
try {
|
||||
const res = await api.listFiles(path, pageNum, LIMIT);
|
||||
const entries = res.entries ?? [];
|
||||
if (pageNum === 1) {
|
||||
setEntries(res.entries);
|
||||
setEntries(entries);
|
||||
} else {
|
||||
setEntries(prev => [...prev, ...res.entries]);
|
||||
setEntries(prev => [...prev, ...entries]);
|
||||
}
|
||||
setPage(pageNum);
|
||||
setHasMore(res.has_more);
|
||||
@@ -165,7 +166,7 @@ export default function FileBrowserModal({ initialPath, onSelect, onClose }: Pro
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
{roots.length > 1 && (
|
||||
{(roots ?? []).length > 1 && (
|
||||
<select
|
||||
className="input w-auto"
|
||||
value={roots.includes(currentPath) ? currentPath : roots[0]}
|
||||
@@ -270,7 +271,7 @@ export default function FileBrowserModal({ initialPath, onSelect, onClose }: Pro
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{entries.length === 0 && !loading && (
|
||||
{(entries ?? []).length === 0 && !loading && (
|
||||
<tr>
|
||||
<td colSpan={4} className="px-3 py-6 text-center text-slate-500">
|
||||
Directorio vacío
|
||||
@@ -290,7 +291,7 @@ export default function FileBrowserModal({ initialPath, onSelect, onClose }: Pro
|
||||
|
||||
{hasMore && (
|
||||
<button className="btn-ghost w-full text-xs" onClick={() => loadDir(currentPath, page + 1)}>
|
||||
Cargar más ({total - entries.length} restantes)
|
||||
Cargar más ({total - (entries ?? []).length} restantes)
|
||||
</button>
|
||||
)}
|
||||
|
||||
|
||||
+13
-12
@@ -65,10 +65,10 @@ export default function Files() {
|
||||
useEffect(() => {
|
||||
api.fileCapabilities().then(setCapabilities).catch(() => {});
|
||||
api.fileRoots().then(r => {
|
||||
setRoots(r.roots);
|
||||
setRoots(r.roots ?? []);
|
||||
setUnrestricted(r.unrestricted);
|
||||
if (r.roots.length > 0) {
|
||||
setCurrentPath(r.roots[0]);
|
||||
if ((r.roots ?? []).length > 0) {
|
||||
setCurrentPath(r.roots![0]);
|
||||
} else if (!r.unrestricted) {
|
||||
setCurrentPath("/");
|
||||
}
|
||||
@@ -88,10 +88,11 @@ export default function Files() {
|
||||
setSearchQuery("");
|
||||
try {
|
||||
const res = await api.listFiles(path, pageNum, LIMIT);
|
||||
const entries = res.entries ?? [];
|
||||
if (pageNum === 1) {
|
||||
setEntries(res.entries);
|
||||
setEntries(entries);
|
||||
} else {
|
||||
setEntries(prev => [...prev, ...res.entries]);
|
||||
setEntries(prev => [...prev, ...entries]);
|
||||
}
|
||||
setPage(pageNum);
|
||||
setHasMore(res.has_more);
|
||||
@@ -237,7 +238,7 @@ export default function Files() {
|
||||
setSearching(true);
|
||||
try {
|
||||
const res = await api.searchFiles(currentPath, q, 100);
|
||||
setSearchResults(res.results);
|
||||
setSearchResults(res.results ?? []);
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : "Error searching");
|
||||
} finally {
|
||||
@@ -285,7 +286,7 @@ export default function Files() {
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-3 flex-wrap">
|
||||
{roots.length > 1 && (
|
||||
{(roots ?? []).length > 1 && (
|
||||
<select
|
||||
className="input w-auto"
|
||||
value={roots.includes(currentPath) ? currentPath : roots[0]}
|
||||
@@ -328,9 +329,9 @@ export default function Files() {
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{searchResults.length > 0 && (
|
||||
<div className="card">
|
||||
<div className="text-sm text-slate-400 mb-2">Resultados ({searchResults.length})</div>
|
||||
{(searchResults ?? []).length > 0 && (
|
||||
<div className="card">
|
||||
<div className="text-sm text-slate-400 mb-2">Resultados ({(searchResults ?? []).length})</div>
|
||||
<div className="max-h-48 overflow-y-auto">
|
||||
{searchResults.map((hit, i) => (
|
||||
<div
|
||||
@@ -476,7 +477,7 @@ export default function Files() {
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{entries.length === 0 && !loading && (
|
||||
{(entries ?? []).length === 0 && !loading && (
|
||||
<tr>
|
||||
<td colSpan={5} className="px-4 py-8 text-center text-slate-500">
|
||||
{searchQuery ? "Sin resultados" : "Directorio vacío"}
|
||||
@@ -496,7 +497,7 @@ export default function Files() {
|
||||
|
||||
{hasMore && (
|
||||
<button className="btn-ghost w-full text-xs" onClick={() => loadDir(currentPath, page + 1)}>
|
||||
Cargar más ({total - entries.length} restantes)
|
||||
Cargar más ({total - (entries ?? []).length} restantes)
|
||||
</button>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user