Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 30 additions & 23 deletions components/dashboard/account-overview.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,24 +78,31 @@ describe("AccountOverview", () => {
it("renders correctly when wallet is disconnected", async () => {
renderWithWallet(null);

// There are now two connect buttons: header and CTA card
const connectButtons = screen.getAllByRole("button", { name: /connect wallet/i });
expect(connectButtons.length).toBeGreaterThanOrEqual(1);

expect(
screen.getByRole("button", { name: /connect wallet/i }),
screen.getByTestId("account-overview-connect-cta-card"),
).toBeInTheDocument();

expect(
screen.queryByTestId("account-overview-address"),
).not.toBeInTheDocument();

expect(
screen.getByText(
"Connect your Stellar wallet to view balances and send payments.",
),
).toBeInTheDocument();

// Cards load asynchronously — wait for the skeleton to be replaced.
await waitFor(() => {
expect(screen.getByText("Total Balance")).toBeInTheDocument();
expect(screen.getByText("Paid This Month")).toBeInTheDocument();
expect(screen.getByText("To Be Paid")).toBeInTheDocument();
});
expect(
screen.getByText("No Wallet Connected"),
).toBeInTheDocument();

// Cards and skeleton are NOT rendered when disconnected
expect(screen.queryByTestId("summary-cards-grid")).not.toBeInTheDocument();
expect(screen.queryByRole("status", { name: /loading account summary/i })).not.toBeInTheDocument();
});

it("renders correctly when wallet is connected", () => {
Expand All @@ -118,7 +125,7 @@ describe("AccountOverview", () => {
it("connects from the disconnected state using the wallet context handler", () => {
renderWithWallet(null);

fireEvent.click(screen.getByRole("button", { name: /connect wallet/i }));
fireEvent.click(screen.getByTestId("account-overview-connect-cta-card"));

expect(screen.getByTestId("account-overview-address")).toHaveTextContent(
/^G[A-Z0-9]{3}\.\.\.[A-Z0-9]{4}$/,
Expand Down Expand Up @@ -183,7 +190,7 @@ describe("AccountOverview – loading state", () => {
);

render(
<WalletProvider initialAddress={null}>
<WalletProvider initialAddress={PUBLIC_ADDRESS}>
<AccountOverview />
</WalletProvider>,
);
Expand All @@ -201,7 +208,7 @@ describe("AccountOverview – loading state", () => {
);

render(
<WalletProvider initialAddress={null}>
<WalletProvider initialAddress={PUBLIC_ADDRESS}>
<AccountOverview />
</WalletProvider>,
);
Expand All @@ -219,7 +226,7 @@ describe("AccountOverview – loading state", () => {
);

render(
<WalletProvider initialAddress={null}>
<WalletProvider initialAddress={PUBLIC_ADDRESS}>
<AccountOverview />
</WalletProvider>,
);
Expand All @@ -236,7 +243,7 @@ describe("AccountOverview – loading state", () => {
);

render(
<WalletProvider initialAddress={null}>
<WalletProvider initialAddress={PUBLIC_ADDRESS}>
<AccountOverview />
</WalletProvider>,
);
Expand All @@ -261,7 +268,7 @@ describe("AccountOverview – success state", () => {

it("renders the cards grid after data resolves", async () => {
render(
<WalletProvider initialAddress={null}>
<WalletProvider initialAddress={PUBLIC_ADDRESS}>
<AccountOverview />
</WalletProvider>,
);
Expand All @@ -273,7 +280,7 @@ describe("AccountOverview – success state", () => {

it("removes the skeleton after data resolves", async () => {
render(
<WalletProvider initialAddress={null}>
<WalletProvider initialAddress={PUBLIC_ADDRESS}>
<AccountOverview />
</WalletProvider>,
);
Expand All @@ -287,7 +294,7 @@ describe("AccountOverview – success state", () => {

it("renders all three summary card titles after load", async () => {
render(
<WalletProvider initialAddress={null}>
<WalletProvider initialAddress={PUBLIC_ADDRESS}>
<AccountOverview />
</WalletProvider>,
);
Expand All @@ -301,7 +308,7 @@ describe("AccountOverview – success state", () => {

it("does not show the error state after a successful load", async () => {
render(
<WalletProvider initialAddress={null}>
<WalletProvider initialAddress={PUBLIC_ADDRESS}>
<AccountOverview />
</WalletProvider>,
);
Expand Down Expand Up @@ -343,7 +350,7 @@ describe("AccountOverview – error state", () => {
forceLoadError();

render(
<WalletProvider initialAddress={null}>
<WalletProvider initialAddress={PUBLIC_ADDRESS}>
<AccountOverview />
</WalletProvider>,
);
Expand All @@ -357,7 +364,7 @@ describe("AccountOverview – error state", () => {
forceLoadError();

render(
<WalletProvider initialAddress={null}>
<WalletProvider initialAddress={PUBLIC_ADDRESS}>
<AccountOverview />
</WalletProvider>,
);
Expand All @@ -371,7 +378,7 @@ describe("AccountOverview – error state", () => {
forceLoadError("Failed to load account summary.");

render(
<WalletProvider initialAddress={null}>
<WalletProvider initialAddress={PUBLIC_ADDRESS}>
<AccountOverview />
</WalletProvider>,
);
Expand All @@ -387,7 +394,7 @@ describe("AccountOverview – error state", () => {
forceLoadError();

render(
<WalletProvider initialAddress={null}>
<WalletProvider initialAddress={PUBLIC_ADDRESS}>
<AccountOverview />
</WalletProvider>,
);
Expand All @@ -405,7 +412,7 @@ describe("AccountOverview – error state", () => {
forceLoadError();

render(
<WalletProvider initialAddress={null}>
<WalletProvider initialAddress={PUBLIC_ADDRESS}>
<AccountOverview />
</WalletProvider>,
);
Expand All @@ -422,7 +429,7 @@ describe("AccountOverview – error state", () => {
forceLoadError();

render(
<WalletProvider initialAddress={null}>
<WalletProvider initialAddress={PUBLIC_ADDRESS}>
<AccountOverview />
</WalletProvider>,
);
Expand All @@ -444,7 +451,7 @@ describe("AccountOverview – error state", () => {
.mockReturnValue(summaryDataModule.summaryCardsData);

render(
<WalletProvider initialAddress={null}>
<WalletProvider initialAddress={PUBLIC_ADDRESS}>
<AccountOverview />
</WalletProvider>,
);
Expand Down
61 changes: 43 additions & 18 deletions components/dashboard/account-overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -279,26 +279,51 @@ export default function AccountOverview() {
</div>

{/* Cards Grid — loading / error / success */}
{summaryState.status === "loading" && (
<SummaryCardsSkeleton shade="dark" />
)}
{isConnected ? (
<>
{summaryState.status === "loading" && (
<SummaryCardsSkeleton shade="dark" />
)}

{summaryState.status === "error" && (
<ErrorState
title="Failed to Load"
description={summaryState.message}
onRetry={loadSummary}
/>
)}
{summaryState.status === "error" && (
<ErrorState
title="Failed to Load"
description={summaryState.message}
onRetry={loadSummary}
/>
)}

{summaryState.status === "success" && (
<div
data-testid="summary-cards-grid"
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
>
{summaryState.cards.map((card) => (
<AccountSummaryCard key={card.title} {...card} />
))}
{summaryState.status === "success" && (
<div
data-testid="summary-cards-grid"
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
>
{summaryState.cards.map((card) => (
<AccountSummaryCard key={card.title} {...card} />
))}
</div>
)}
</>
) : (
<div className="bg-zinc-100 dark:bg-zinc-800/50 rounded-xl p-8 sm:p-12 flex flex-col items-center justify-center text-center border border-zinc-200 dark:border-zinc-700/50 min-h-[300px]">
<div className="w-16 h-16 bg-zinc-200 dark:bg-zinc-800 rounded-full flex items-center justify-center mb-6">
<Wallet className="w-8 h-8 text-zinc-500 dark:text-zinc-400" aria-hidden="true" />
</div>
<h3 className="text-xl md:text-2xl font-bold text-zinc-900 dark:text-white mb-3">
No Wallet Connected
</h3>
<p className="text-zinc-500 dark:text-zinc-400 mb-8 max-w-md text-base md:text-lg">
Connect your Stellar wallet to view your balances, manage assets, and securely sign transactions.
</p>
<button
type="button"
onClick={handleConnect}
data-testid="account-overview-connect-cta-card"
className="flex items-center gap-3 px-8 py-4 bg-zinc-900 dark:bg-white text-white dark:text-zinc-900 rounded-xl font-semibold hover:opacity-90 transition-opacity focus:outline-none focus-visible:ring-2 focus-visible:ring-zinc-400 focus-visible:ring-offset-2 dark:focus-visible:ring-zinc-500 cursor-pointer shadow-lg"
>
<Wallet className="w-5 h-5" aria-hidden="true" />
Connect Wallet
</button>
</div>
)}

Expand Down
20 changes: 20 additions & 0 deletions design/dashboard-redesign.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,26 @@ npx vitest run components/dashboard/account-overview.test.tsx --coverage.enabled

---

## Disconnected State CTA

**Branch:** `feat/account-overview-connect-cta`

### What was added

The `AccountOverview` component now displays a "Connect Wallet" call-to-action (CTA) card in place of the balance summary cards when no wallet is connected. This explicitly prompts users to connect, rather than showing a skeleton loader or empty data.

### Accessibility (WCAG 2.1 AA)

- **Contrast**: The CTA utilizes `bg-zinc-900` (`dark:bg-white`) for the button with `text-white` (`dark:text-zinc-900`), ensuring high contrast across both themes.
- **Keyboard Nav**: The "Connect Wallet" button inside the card uses native `<button>` functionality and applies the standard `focus-visible:ring-2 focus-visible:ring-zinc-400 focus-visible:ring-offset-2 dark:focus-visible:ring-zinc-500` ring, ensuring clear focus states.
- **ARIA**: Icons inside the card (e.g., `Wallet`) are marked with `aria-hidden="true"` since they are purely decorative and the action is described by text.

### Responsive behaviour

The card dynamically adjusts its padding (`p-8 sm:p-12`), heading text size (`text-xl md:text-2xl`), and description text size (`text-base md:text-lg`) based on viewport breakpoints. It uses `min-h-[300px]` to maintain structural integrity of the dashboard even when disconnected.

---

## Drag-and-Drop Widget Reordering

**Branch:** `feature/dashboard-widget-reordering`
Expand Down