Skip to content

Commit c48a98b

Browse files
authored
refactor: enhance ProvidersCard and ProviderList components for better error handling and data safety
refactor: enhance ProvidersCard and ProviderList components for better error handling and data safety
2 parents fae1487 + af6d115 commit c48a98b

File tree

3 files changed

+165
-108
lines changed

3 files changed

+165
-108
lines changed

src/components/ecosystem-pages/providers-page/card.tsx

Lines changed: 110 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,34 @@ import { useIntl } from "react-intl";
1010
import { Uptime } from "./uptime";
1111

1212
export default function ProvidersCard({ provider }: any) {
13-
const activeCPU = provider.isOnline ? provider.activeStats.cpu / 1000 : 0;
14-
const pendingCPU = provider.isOnline ? provider.pendingStats.cpu / 1000 : 0;
15-
const totalCPU = provider.isOnline
16-
? (provider.availableStats.cpu +
17-
provider.pendingStats.cpu +
18-
provider.activeStats.cpu) /
13+
// Safely access stats with defaults
14+
const activeStats = provider.activeStats || { cpu: 0, memory: 0, gpu: 0, storage: 0 };
15+
const pendingStats = provider.pendingStats || { cpu: 0, memory: 0, gpu: 0, storage: 0 };
16+
const availableStats = provider.availableStats || { cpu: 0, memory: 0, gpu: 0, storage: 0 };
17+
18+
const isOnline = provider.isOnline ?? false;
19+
20+
const activeCPU = isOnline ? (activeStats.cpu || 0) / 1000 : 0;
21+
const pendingCPU = isOnline ? (pendingStats.cpu || 0) / 1000 : 0;
22+
const totalCPU = isOnline
23+
? ((availableStats.cpu || 0) +
24+
(pendingStats.cpu || 0) +
25+
(activeStats.cpu || 0)) /
1926
1000
2027
: 0;
2128

22-
const gpuModels = provider.hardwareGpuModels.map((gpu: any) =>
29+
const gpuModels = (provider.hardwareGpuModels || []).map((gpu: any) =>
2330
gpu.substring(gpu.lastIndexOf(" ") + 1, gpu.length),
2431
);
2532

26-
const _activeMemory = provider.isOnline
27-
? bytesToShrink(provider.activeStats.memory + provider.pendingStats.memory)
33+
const _activeMemory = isOnline && (activeStats.memory || pendingStats.memory)
34+
? bytesToShrink((activeStats.memory || 0) + (pendingStats.memory || 0))
2835
: null;
29-
const _totalMemory = provider.isOnline
36+
const _totalMemory = isOnline && (availableStats.memory || pendingStats.memory || activeStats.memory)
3037
? bytesToShrink(
31-
provider.availableStats.memory +
32-
provider.pendingStats.memory +
33-
provider.activeStats.memory,
38+
(availableStats.memory || 0) +
39+
(pendingStats.memory || 0) +
40+
(activeStats.memory || 0),
3441
)
3542
: null;
3643

@@ -42,23 +49,23 @@ export default function ProvidersCard({ provider }: any) {
4249
<div className="flex w-full flex-col overflow-hidden rounded-lg border bg-background2 p-4">
4350
<div className="flex gap-x-[10px]">
4451
<div className="flex h-12 w-12 items-center justify-center rounded border bg-background text-xl font-extrabold uppercase">
45-
{name?.[0]}
46-
{name?.[1]}
52+
{name?.[0] || provider.owner?.[5] || "?"}
53+
{name?.[1] || provider.owner?.[6] || ""}
4754
</div>
4855

4956
<div>
5057
<p className="break-words text-base font-semibold text-foreground">
51-
{provider.name?.length > 20 ? (
58+
{provider.name && provider.name.length > 20 ? (
5259
<span>{getSplitText(provider.name, 4, 13)}</span>
5360
) : (
54-
<span>{provider.name}</span>
61+
<span>{provider.name || provider.owner || "Unknown Provider"}</span>
5562
)}
5663
</p>
5764
<p className="break-words text-xs text-cardGray">
58-
{provider.hostUri?.length > 20 ? (
65+
{provider.hostUri && provider.hostUri.length > 20 ? (
5966
<span>{getSplitText(provider.hostUri, 4, 13)}</span>
6067
) : (
61-
<span>{provider.hostUri}</span>
68+
<span>{provider.hostUri || provider.owner || ""}</span>
6269
)}
6370
</p>
6471
</div>
@@ -76,89 +83,99 @@ export default function ProvidersCard({ provider }: any) {
7683
</p>
7784
</div>
7885

79-
<div className="mt-3 flex flex-col items-center justify-between ">
80-
<div className="flex w-full items-center justify-between">
81-
<p className="text-xs font-medium">{`Uptime: ${intl.formatNumber(
82-
provider.uptime7d,
83-
{
84-
style: "percent",
85-
maximumFractionDigits: 0,
86-
},
87-
)}`}</p>
88-
<p className="text-xs font-medium text-foreground">7 D</p>
89-
</div>
86+
{provider.uptime7d !== undefined && (
87+
<div className="mt-3 flex flex-col items-center justify-between ">
88+
<div className="flex w-full items-center justify-between">
89+
<p className="text-xs font-medium">{`Uptime: ${intl.formatNumber(
90+
provider.uptime7d || 0,
91+
{
92+
style: "percent",
93+
maximumFractionDigits: 0,
94+
},
95+
)}`}</p>
96+
<p className="text-xs font-medium text-foreground">7 D</p>
97+
</div>
9098

91-
<div className=" mt-3 w-full">
92-
<Uptime value={provider.uptime7d} />
99+
<div className=" mt-3 w-full">
100+
<Uptime value={provider.uptime7d || 0} />
101+
</div>
93102
</div>
94-
</div>
103+
)}
95104

96105
<div className=" mt-3 flex flex-col gap-y-[6px]">
97-
<Stats
98-
componentName="CPU:"
99-
isOver60Percent={
100-
Math.round(((activeCPU + pendingCPU) / totalCPU) * 100) > 60
101-
}
102-
value={`${Math.round(activeCPU + pendingCPU)} / ${Math.round(
103-
totalCPU,
104-
)}`}
105-
/>
106+
{totalCPU > 0 && (
107+
<Stats
108+
componentName="CPU:"
109+
isOver60Percent={
110+
totalCPU > 0 && Math.round(((activeCPU + pendingCPU) / totalCPU) * 100) > 60
111+
}
112+
value={`${Math.round(activeCPU + pendingCPU)} / ${Math.round(
113+
totalCPU,
114+
)}`}
115+
/>
116+
)}
106117

107-
<div className="flex w-full items-center justify-between rounded-sm border p-2">
108-
<p className=" text-xs font-medium">GPU:</p>
118+
{gpuModels.length > 0 && (
119+
<div className="flex w-full items-center justify-between rounded-sm border p-2">
120+
<p className=" text-xs font-medium">GPU:</p>
109121

110-
<div className="flex items-center justify-center gap-x-1">
111-
{gpuModels.slice(0, 1).map((gpu: any, i: any) => (
112-
<p
113-
key={i}
114-
className="rounded-full border bg-[#F4F4F4] px-2 text-2xs font-bold text-cardGray dark:bg-darkGray dark:text-para"
115-
>
116-
{gpu}
117-
</p>
118-
))}
122+
<div className="flex items-center justify-center gap-x-1">
123+
{gpuModels.slice(0, 1).map((gpu: any, i: any) => (
124+
<p
125+
key={i}
126+
className="rounded-full border bg-[#F4F4F4] px-2 text-2xs font-bold text-cardGray dark:bg-darkGray dark:text-para"
127+
>
128+
{gpu}
129+
</p>
130+
))}
119131

120-
{gpuModels.length > 2 && (
121-
<HoverCard>
122-
<HoverCardTrigger>
123-
<p className="rounded-full border bg-[#F4F4F4] px-2 text-xs font-bold text-cardGray dark:bg-darkGray dark:text-para">
124-
{`+${gpuModels.length - 1}`}
125-
</p>
126-
</HoverCardTrigger>
127-
<HoverCardContent>
128-
<div className="flex w-52 flex-wrap gap-x-2 gap-y-2 rounded-lg bg-background2 p-2">
129-
{gpuModels.slice(1).map((gpu: any, i: any) => (
130-
<p
131-
key={i}
132-
className="rounded-full border bg-[#F4F4F4] px-2 text-xs font-bold text-cardGray"
133-
>
134-
{gpu}
135-
</p>
136-
))}
137-
</div>
138-
</HoverCardContent>
139-
</HoverCard>
140-
)}
132+
{gpuModels.length > 2 && (
133+
<HoverCard>
134+
<HoverCardTrigger>
135+
<p className="rounded-full border bg-[#F4F4F4] px-2 text-xs font-bold text-cardGray dark:bg-darkGray dark:text-para">
136+
{`+${gpuModels.length - 1}`}
137+
</p>
138+
</HoverCardTrigger>
139+
<HoverCardContent>
140+
<div className="flex w-52 flex-wrap gap-x-2 gap-y-2 rounded-lg bg-background2 p-2">
141+
{gpuModels.slice(1).map((gpu: any, i: any) => (
142+
<p
143+
key={i}
144+
className="rounded-full border bg-[#F4F4F4] px-2 text-xs font-bold text-cardGray"
145+
>
146+
{gpu}
147+
</p>
148+
))}
149+
</div>
150+
</HoverCardContent>
151+
</HoverCard>
152+
)}
153+
</div>
141154
</div>
142-
</div>
155+
)}
143156

144-
<Stats
145-
componentName="Memory:"
146-
value={`${roundDecimal(
147-
_activeMemory?.value as number,
148-
0,
149-
)} ${_activeMemory?.unit} / ${roundDecimal(
150-
_totalMemory?.value as number,
151-
0,
152-
)} ${_totalMemory?.unit} `}
153-
isOver60Percent={
154-
(provider.activeStats.memory + provider.pendingStats.memory) /
155-
(provider.availableStats.memory +
156-
provider.pendingStats.memory +
157-
provider.activeStats.memory) >
158-
0.64
159-
}
160-
/>
161-
<Stats componentName="Active Leases:" value={provider.leaseCount} />
157+
{_activeMemory && _totalMemory && (
158+
<Stats
159+
componentName="Memory:"
160+
value={`${roundDecimal(
161+
_activeMemory?.value as number,
162+
0,
163+
)} ${_activeMemory?.unit} / ${roundDecimal(
164+
_totalMemory?.value as number,
165+
0,
166+
)} ${_totalMemory?.unit} `}
167+
isOver60Percent={
168+
((activeStats.memory || 0) + (pendingStats.memory || 0)) /
169+
((availableStats.memory || 0) +
170+
(pendingStats.memory || 0) +
171+
(activeStats.memory || 0)) >
172+
0.64
173+
}
174+
/>
175+
)}
176+
{provider.leaseCount !== undefined && (
177+
<Stats componentName="Active Leases:" value={provider.leaseCount || 0} />
178+
)}
162179

163180
{provider.ipRegion && provider.ipCountry && (
164181
<Stats

src/components/ecosystem-pages/providers-page/dashboard.tsx

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export default function ProvidersList({ pathName }: ProviderListProps) {
4141
}
4242

4343
function ProviderListContent({ pathName }: ProviderListProps) {
44-
const { data: providers, isLoading: isLoadingProviders } = useProviderList();
44+
const { data: providers, isLoading: isLoadingProviders, error } = useProviderList();
4545

4646
const [page, setPage] = useState(1);
4747
const [sort, setSort] = useState<string>("active-leases-desc");
@@ -56,7 +56,10 @@ function ProviderListContent({ pathName }: ProviderListProps) {
5656
const pageCount = Math.ceil(filteredProviders.length / ROWS_PER_PAGE);
5757

5858
useEffect(() => {
59-
if (!providers) return;
59+
if (!providers || !Array.isArray(providers)) {
60+
setFilteredProviders([]);
61+
return;
62+
}
6063

6164
let filtered = [...providers];
6265

@@ -65,31 +68,41 @@ function ProviderListContent({ pathName }: ProviderListProps) {
6568
filtered = filtered.filter(
6669
(provider) =>
6770
provider.hostUri?.toLowerCase().includes(searchLower) ||
68-
provider.owner?.toLowerCase().includes(searchLower),
71+
provider.owner?.toLowerCase().includes(searchLower) ||
72+
provider.name?.toLowerCase().includes(searchLower),
6973
);
7074
}
7175

7276
if (isFilteringActive) {
73-
filtered = filtered.filter((provider) => provider.isOnline);
77+
// Only filter by isOnline if it exists, otherwise include all
78+
filtered = filtered.filter((provider) =>
79+
provider.isOnline !== undefined ? provider.isOnline : true
80+
);
7481
}
7582

7683
if (isFilteringAudited) {
7784
filtered = filtered.filter(
78-
(provider) => provider.isAudited && provider.isOnline,
85+
(provider) =>
86+
provider.isAudited &&
87+
(provider.isOnline !== undefined ? provider.isOnline : true),
7988
);
8089
}
8190

8291
filtered.sort((a, b) => {
8392
switch (sort) {
8493
case "active-leases-desc":
85-
return b.leaseCount - a.leaseCount;
94+
return (b.leaseCount || 0) - (a.leaseCount || 0);
8695
case "active-leases-asc":
87-
return a.leaseCount - b.leaseCount;
96+
return (a.leaseCount || 0) - (b.leaseCount || 0);
8897
case "gpu-available-desc":
8998
const totalGpuB =
90-
b.availableStats.gpu + b.pendingStats.gpu + b.activeStats.gpu;
99+
(b.availableStats?.gpu || 0) +
100+
(b.pendingStats?.gpu || 0) +
101+
(b.activeStats?.gpu || 0);
91102
const totalGpuA =
92-
a.availableStats.gpu + a.pendingStats.gpu + a.activeStats.gpu;
103+
(a.availableStats?.gpu || 0) +
104+
(a.pendingStats?.gpu || 0) +
105+
(a.activeStats?.gpu || 0);
93106
return totalGpuB - totalGpuA;
94107
default:
95108
return 0;
@@ -122,6 +135,16 @@ function ProviderListContent({ pathName }: ProviderListProps) {
122135
);
123136
}
124137

138+
if (error) {
139+
return (
140+
<div className="container mx-auto px-4">
141+
<div className="py-6 text-center text-red-500">
142+
Error loading providers: {error.message}
143+
</div>
144+
</div>
145+
);
146+
}
147+
125148
return (
126149
<div className="container mx-auto px-4">
127150
<div className="mb-6 flex flex-col items-center justify-between md:flex-row">
@@ -139,7 +162,11 @@ function ProviderListContent({ pathName }: ProviderListProps) {
139162
</div>
140163

141164
{currentPageProviders.length === 0 ? (
142-
<p className="py-6 text-center text-gray-500">No providers found</p>
165+
<p className="py-6 text-center text-gray-500">
166+
{providers && providers.length > 0
167+
? "No providers match the current filters"
168+
: "No providers found"}
169+
</p>
143170
) : (
144171
<>
145172
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,31 @@
11
import { BASE_API_URL } from "@/lib/constants";
2+
import type { ApiProviderList } from "@/types/provider";
23
import { useQuery } from "@tanstack/react-query";
34
import axios from "axios";
45

56
const isProd = import.meta.env.PROD;
67

7-
export async function getProviderList() {
8-
const response = await axios.get(`${BASE_API_URL}/providers`);
9-
10-
return response.data;
8+
export async function getProviderList(): Promise<ApiProviderList[]> {
9+
try {
10+
const response = await axios.get(`${BASE_API_URL}/providers`);
11+
12+
// Handle case where response.data might be an array directly or wrapped
13+
const data = Array.isArray(response.data) ? response.data : response.data?.data || response.data?.providers || [];
14+
15+
return data;
16+
} catch (error) {
17+
console.error("Error fetching provider list:", error);
18+
throw error;
19+
}
1120
}
1221

1322
export function useProviderList() {
14-
return useQuery(["PROVIDER_LIST"], () => getProviderList(), {
23+
return useQuery<ApiProviderList[], Error>({
24+
queryKey: ["PROVIDER_LIST"],
25+
queryFn: getProviderList,
1526
refetchInterval: 2000,
1627
refetchIntervalInBackground: true,
28+
retry: 3,
29+
retryDelay: 1000,
1730
});
1831
}

0 commit comments

Comments
 (0)