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;
|
return undefined as T;
|
||||||
}
|
}
|
||||||
const text = await res.text();
|
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) {
|
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;
|
return data as T;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ export default function FileBrowserModal({ initialPath, onSelect, onClose }: Pro
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.fileCapabilities().then(setCapabilities).catch(() => {});
|
api.fileCapabilities().then(setCapabilities).catch(() => {});
|
||||||
api.fileRoots().then(r => {
|
api.fileRoots().then(r => {
|
||||||
setRoots(r.roots);
|
setRoots(r.roots ?? []);
|
||||||
if (!initialPath && r.roots.length > 0) {
|
if (!initialPath && (r.roots ?? []).length > 0) {
|
||||||
setCurrentPath(r.roots[0]);
|
setCurrentPath(r.roots![0]);
|
||||||
}
|
}
|
||||||
}).catch(() => {});
|
}).catch(() => {});
|
||||||
}, []);
|
}, []);
|
||||||
@@ -63,10 +63,11 @@ export default function FileBrowserModal({ initialPath, onSelect, onClose }: Pro
|
|||||||
setError(null);
|
setError(null);
|
||||||
try {
|
try {
|
||||||
const res = await api.listFiles(path, pageNum, LIMIT);
|
const res = await api.listFiles(path, pageNum, LIMIT);
|
||||||
|
const entries = res.entries ?? [];
|
||||||
if (pageNum === 1) {
|
if (pageNum === 1) {
|
||||||
setEntries(res.entries);
|
setEntries(entries);
|
||||||
} else {
|
} else {
|
||||||
setEntries(prev => [...prev, ...res.entries]);
|
setEntries(prev => [...prev, ...entries]);
|
||||||
}
|
}
|
||||||
setPage(pageNum);
|
setPage(pageNum);
|
||||||
setHasMore(res.has_more);
|
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">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
{roots.length > 1 && (
|
{(roots ?? []).length > 1 && (
|
||||||
<select
|
<select
|
||||||
className="input w-auto"
|
className="input w-auto"
|
||||||
value={roots.includes(currentPath) ? currentPath : roots[0]}
|
value={roots.includes(currentPath) ? currentPath : roots[0]}
|
||||||
@@ -270,7 +271,7 @@ export default function FileBrowserModal({ initialPath, onSelect, onClose }: Pro
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
{entries.length === 0 && !loading && (
|
{(entries ?? []).length === 0 && !loading && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={4} className="px-3 py-6 text-center text-slate-500">
|
<td colSpan={4} className="px-3 py-6 text-center text-slate-500">
|
||||||
Directorio vacío
|
Directorio vacío
|
||||||
@@ -290,7 +291,7 @@ export default function FileBrowserModal({ initialPath, onSelect, onClose }: Pro
|
|||||||
|
|
||||||
{hasMore && (
|
{hasMore && (
|
||||||
<button className="btn-ghost w-full text-xs" onClick={() => loadDir(currentPath, page + 1)}>
|
<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>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
+12
-11
@@ -65,10 +65,10 @@ export default function Files() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.fileCapabilities().then(setCapabilities).catch(() => {});
|
api.fileCapabilities().then(setCapabilities).catch(() => {});
|
||||||
api.fileRoots().then(r => {
|
api.fileRoots().then(r => {
|
||||||
setRoots(r.roots);
|
setRoots(r.roots ?? []);
|
||||||
setUnrestricted(r.unrestricted);
|
setUnrestricted(r.unrestricted);
|
||||||
if (r.roots.length > 0) {
|
if ((r.roots ?? []).length > 0) {
|
||||||
setCurrentPath(r.roots[0]);
|
setCurrentPath(r.roots![0]);
|
||||||
} else if (!r.unrestricted) {
|
} else if (!r.unrestricted) {
|
||||||
setCurrentPath("/");
|
setCurrentPath("/");
|
||||||
}
|
}
|
||||||
@@ -88,10 +88,11 @@ export default function Files() {
|
|||||||
setSearchQuery("");
|
setSearchQuery("");
|
||||||
try {
|
try {
|
||||||
const res = await api.listFiles(path, pageNum, LIMIT);
|
const res = await api.listFiles(path, pageNum, LIMIT);
|
||||||
|
const entries = res.entries ?? [];
|
||||||
if (pageNum === 1) {
|
if (pageNum === 1) {
|
||||||
setEntries(res.entries);
|
setEntries(entries);
|
||||||
} else {
|
} else {
|
||||||
setEntries(prev => [...prev, ...res.entries]);
|
setEntries(prev => [...prev, ...entries]);
|
||||||
}
|
}
|
||||||
setPage(pageNum);
|
setPage(pageNum);
|
||||||
setHasMore(res.has_more);
|
setHasMore(res.has_more);
|
||||||
@@ -237,7 +238,7 @@ export default function Files() {
|
|||||||
setSearching(true);
|
setSearching(true);
|
||||||
try {
|
try {
|
||||||
const res = await api.searchFiles(currentPath, q, 100);
|
const res = await api.searchFiles(currentPath, q, 100);
|
||||||
setSearchResults(res.results);
|
setSearchResults(res.results ?? []);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
setError(e instanceof Error ? e.message : "Error searching");
|
setError(e instanceof Error ? e.message : "Error searching");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -285,7 +286,7 @@ export default function Files() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-3 flex-wrap">
|
<div className="flex items-center gap-3 flex-wrap">
|
||||||
{roots.length > 1 && (
|
{(roots ?? []).length > 1 && (
|
||||||
<select
|
<select
|
||||||
className="input w-auto"
|
className="input w-auto"
|
||||||
value={roots.includes(currentPath) ? currentPath : roots[0]}
|
value={roots.includes(currentPath) ? currentPath : roots[0]}
|
||||||
@@ -328,9 +329,9 @@ export default function Files() {
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{searchResults.length > 0 && (
|
{(searchResults ?? []).length > 0 && (
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="text-sm text-slate-400 mb-2">Resultados ({searchResults.length})</div>
|
<div className="text-sm text-slate-400 mb-2">Resultados ({(searchResults ?? []).length})</div>
|
||||||
<div className="max-h-48 overflow-y-auto">
|
<div className="max-h-48 overflow-y-auto">
|
||||||
{searchResults.map((hit, i) => (
|
{searchResults.map((hit, i) => (
|
||||||
<div
|
<div
|
||||||
@@ -476,7 +477,7 @@ export default function Files() {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
{entries.length === 0 && !loading && (
|
{(entries ?? []).length === 0 && !loading && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={5} className="px-4 py-8 text-center text-slate-500">
|
<td colSpan={5} className="px-4 py-8 text-center text-slate-500">
|
||||||
{searchQuery ? "Sin resultados" : "Directorio vacío"}
|
{searchQuery ? "Sin resultados" : "Directorio vacío"}
|
||||||
@@ -496,7 +497,7 @@ export default function Files() {
|
|||||||
|
|
||||||
{hasMore && (
|
{hasMore && (
|
||||||
<button className="btn-ghost w-full text-xs" onClick={() => loadDir(currentPath, page + 1)}>
|
<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>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user