Skip to content

Commit cf3568d

Browse files
authored
Merge pull request #170 from UzyKhs/fix/88-frontend-add-loading-empty-and-error-states-for-all-major-views
[88] Frontend: Add loading, empty, and error states for all major views
2 parents b570181 + bfc915c commit cf3568d

7 files changed

Lines changed: 52 additions & 5 deletions

File tree

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"dependencies": {
2020
"@stellar/freighter-api": "^6.0.1",
2121
"@stellar/stellar-sdk": "^14.6.1",
22+
"es-toolkit": "^1.33.0",
2223
"lucide-react": "^0.577.0",
2324
"react": "^19.2.0",
2425
"react-dom": "^19.2.0",
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import type { ReactNode } from "react";
2+
3+
interface ViewStateProps {
4+
title: string;
5+
description: string;
6+
tone?: "default" | "error";
7+
action?: ReactNode;
8+
}
9+
10+
export default function ViewState({
11+
title,
12+
description,
13+
tone = "default",
14+
action,
15+
}: ViewStateProps) {
16+
return (
17+
<div
18+
className={`view-state ${tone === "error" ? "view-state-error" : ""}`}
19+
role={tone === "error" ? "alert" : "status"}
20+
aria-live="polite"
21+
>
22+
<h2 className="view-state-title">{title}</h2>
23+
<p className="view-state-description">{description}</p>
24+
{action ? <div className="view-state-action">{action}</div> : null}
25+
</div>
26+
);
27+
}

frontend/src/pages/Analytics.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,20 @@ import { Activity } from "../components/icons";
33
import ApiStatusBanner from "../components/ApiStatusBanner";
44
import PageHeader from "../components/PageHeader";
55
import { useVault } from "../context/VaultContext";
6+
import ViewState from "../components/ViewState";
67

78
const Analytics: React.FC = () => {
89
const { formattedTvl, summary, error, isLoading } = useVault();
910

1011
return (
1112
<div className="glass-panel" style={{ padding: '32px' }}>
1213
{error && <ApiStatusBanner error={error} />}
14+
{isLoading && !error && (
15+
<ViewState
16+
title="Loading analytics"
17+
description="Preparing current pool health and performance metrics."
18+
/>
19+
)}
1320

1421
<PageHeader
1522
title={<span className="text-gradient">Project Analytics</span>}

frontend/src/pages/Portfolio.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ describe("Portfolio", () => {
160160
});
161161

162162
it("supports keyboard sorting and pagination state from the URL", async () => {
163-
renderPortfolio("/portfolio?page=2&pageSize=4&sortBy=asset&direction=asc");
163+
renderPortfolio("/portfolio?page=2&pageSize=4&sortBy=asset&sortDirection=asc");
164164

165165
expect(await screen.findByText(/Yield Bearing Cash/i)).toBeInTheDocument();
166166
expect(screen.getByText(/USDC Treasury Pool/i)).toBeInTheDocument();
@@ -173,7 +173,7 @@ describe("Portfolio", () => {
173173
"sortBy=asset",
174174
);
175175
expect(screen.getByTestId("location-display")).toHaveTextContent(
176-
"direction=desc",
176+
"sortDirection=desc",
177177
);
178178
expect(screen.getByTestId("location-display")).toHaveTextContent("page=1");
179179
});

frontend/src/pages/TransactionHistory.test.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,11 +220,13 @@ describe("TransactionHistory", () => {
220220
await waitFor(() => expect(screen.getByRole("table")).toBeInTheDocument());
221221

222222
// Navigate to page 2
223-
const nextBtn = screen.getByRole("button", { name: /Next/i });
223+
const nextBtn = screen.getByRole("button", { name: /Go to next page/i });
224224
fireEvent.click(nextBtn);
225225

226226
await waitFor(() =>
227-
expect(screen.getByText(/Page 2 of/i)).toBeInTheDocument(),
227+
expect(
228+
screen.getByRole("button", { current: "page", name: /Go to page 2/i }),
229+
).toBeInTheDocument(),
228230
);
229231

230232
// Apply a filter — should reset to page 1
@@ -234,7 +236,9 @@ describe("TransactionHistory", () => {
234236
fireEvent.change(filterSelect, { target: { value: "deposit" } });
235237

236238
await waitFor(() =>
237-
expect(screen.queryByText(/Page 2 of/i)).not.toBeInTheDocument(),
239+
expect(
240+
screen.getByRole("button", { current: "page", name: /Go to page 1/i }),
241+
).toBeInTheDocument(),
238242
);
239243
});
240244

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { sortBy } from "es-toolkit/compat";
2+
3+
export default sortBy;

frontend/vite.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ export default defineConfig(({ mode }) => {
88
const env = loadEnv(mode, process.cwd(), "");
99

1010
return {
11+
resolve: {
12+
alias: {
13+
"es-toolkit/compat/sortBy": "/src/shims/esToolkitSortBy.ts",
14+
},
15+
},
1116
build: {
1217
sourcemap: true,
1318
},

0 commit comments

Comments
 (0)