Skip to content
Open
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
339 changes: 192 additions & 147 deletions app/dashboard/DashboardClient.tsx

Large diffs are not rendered by default.

99 changes: 32 additions & 67 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,82 +28,47 @@ export default function RootLayout({ children }: RootLayoutProps): React.JSX.Ele
return (
<html lang="en" suppressHydrationWarning>
<body className="font-sans antialiased min-h-screen bg-background">
<div className="relative flex min-h-screen flex-col">
<Header />
<div className="flex-1">{children}</div>
<footer className="border-t py-6 mt-8">
<div className="container mx-auto px-4 max-w-5xl">
<div className="flex flex-col sm:flex-row items-center justify-between gap-2 text-xs text-muted-foreground">
<p>
Open-Audit — Open source transparency for the Stellar ecosystem.
</p>
<div className="flex items-center gap-4">
<a
href="https://github.com/your-org/open-audit"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
GitHub
</a>
<a
href="https://github.com/your-org/open-audit/blob/main/CONTRIBUTING.md"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
Contribute
</a>
<a
href="https://stellar.org"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
Stellar.org
</a>
<body className={`${inter.variable} font-sans antialiased min-h-screen bg-background`}>
<NetworkProviderClient>
<LanguageProvider>
<div className="relative flex min-h-screen flex-col">
<Header />
<div className="flex-1">{children}</div>
<Toaster />
<footer className="border-t py-6 mt-8">
<div className="container mx-auto px-4 max-w-5xl">
<div className="flex flex-col sm:flex-row items-center justify-between gap-2 text-xs text-muted-foreground">
<p>
Open-Audit — Open source transparency for the Stellar ecosystem.
</p>
<div className="flex items-center gap-4">
<a
href="https://github.com/your-org/open-audit"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
GitHub
</a>
<a
href="https://github.com/your-org/open-audit/blob/main/CONTRIBUTING.md"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
Contribute
</a>
<a
href="https://stellar.org"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
Stellar.org
</a>
<div className="container mx-auto px-4 max-w-5xl">
<div className="flex flex-col sm:flex-row items-center justify-between gap-2 text-xs text-muted-foreground">
<p>
Open-Audit — Open source transparency for the Stellar ecosystem.
</p>
<div className="flex items-center gap-4">
<a
href="https://github.com/your-org/open-audit"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
GitHub
</a>
<a
href="https://github.com/your-org/open-audit/blob/main/CONTRIBUTING.md"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
Contribute
</a>
<a
href="https://stellar.org"
target="_blank"
rel="noopener noreferrer"
className="hover:text-foreground transition-colors"
>
Stellar.org
</a>
</div>
</div>
</div>
</div>
</footer>
</footer>
</div>
</LanguageProvider>
</NetworkProviderClient>
Expand Down
30 changes: 30 additions & 0 deletions components/dashboard/EmptyState.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from "react";
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { EmptyState } from "./EmptyState";

describe("EmptyState", () => {
it("renders the waiting message", () => {
render(<EmptyState cause="waiting" />);

expect(screen.getByRole("status")).toHaveTextContent("No events found");
expect(screen.getByText("Waiting for events on the Stellar network...")).toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Clear search" })).not.toBeInTheDocument();
});

it("renders the filtered message and clear action", () => {
const onClearSearch = vi.fn();

render(<EmptyState cause="filtered" onClearSearch={onClearSearch} />);

expect(screen.getByText("No events match your search.")).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Clear search" }));
expect(onClearSearch).toHaveBeenCalledTimes(1);
});

it("renders the connection error message", () => {
render(<EmptyState cause="connection-error" />);

expect(screen.getByText("Could not connect to Stellar. Retrying...")).toBeInTheDocument();
});
});
75 changes: 75 additions & 0 deletions components/dashboard/EmptyState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"use client";

import * as React from "react";
import { Radio, Search, WifiOff } from "lucide-react";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";

export type EmptyStateCause = "waiting" | "filtered" | "connection-error";

interface EmptyStateProps {
cause: EmptyStateCause;
onClearSearch?: () => void;
className?: string;
}

const COPY: Record<
EmptyStateCause,
{
title: string;
description: string;
icon: typeof Search;
}
> = {
waiting: {
title: "No events found",
description: "Waiting for events on the Stellar network...",
icon: Radio,
},
filtered: {
title: "No events found",
description: "No events match your search.",
icon: Search,
},
"connection-error": {
title: "No events found",
description: "Could not connect to Stellar. Retrying...",
icon: WifiOff,
},
};

export function EmptyState({
cause,
onClearSearch,
className,
}: EmptyStateProps): React.JSX.Element {
const { title, description, icon: Icon } = COPY[cause];

return (
<div
role="status"
aria-live="polite"
className={cn(
"mx-auto flex min-h-[220px] max-w-md flex-col items-center justify-center px-6 py-10 text-center",
className
)}
>
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded-full border bg-muted/40 text-violet-600 dark:text-violet-400">
<Icon className="h-5 w-5" aria-hidden="true" />
</div>
<h3 className="text-base font-semibold text-foreground">{title}</h3>
<p className="mt-2 text-sm leading-6 text-muted-foreground">{description}</p>
{cause === "filtered" && onClearSearch && (
<Button
type="button"
variant="outline"
size="sm"
className="mt-5"
onClick={onClearSearch}
>
Clear search
</Button>
)}
</div>
);
}
1 change: 1 addition & 0 deletions components/dashboard/EventDetailsModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client"

import * as React from "react"
import { Code, ExternalLink } from "lucide-react"
import {
Dialog,
Expand Down
81 changes: 79 additions & 2 deletions components/dashboard/EventFeedTable.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from "react";
import { render } from "@testing-library/react";
import { fireEvent, render, screen } from "@testing-library/react";
import { describe, it, expect, vi } from "vitest";
import { axe } from "vitest-axe";
import { EventFeedTable } from "./EventFeedTable";
Expand All @@ -15,6 +15,8 @@ describe("EventFeedTable Accessibility", () => {
status: "translated" as const,
description: "Transferred 100 XLM to Bob",
eventType: "transfer",
blueprintName: "Mock Blueprint",
schemaVersion: "test",
raw: {
id: "1",
type: "contract",
Expand All @@ -32,6 +34,8 @@ describe("EventFeedTable Accessibility", () => {
status: "cryptic" as const,
description: "",
eventType: "",
blueprintName: null,
schemaVersion: null,
raw: {
id: "2",
type: "contract",
Expand Down Expand Up @@ -66,6 +70,79 @@ describe("EventFeedTable Accessibility", () => {
);

const results = await axe(container);
expect(results).toHaveNoViolations();
expect(results.violations).toHaveLength(0);
});

it("renders the skeleton while loading", () => {
const columns = {
status: true,
time: true,
description: true,
contract: true,
actions: true,
};

render(
<EventFeedTable
events={[]}
isLoading={true}
columns={columns}
density="comfortable"
onToggleColumn={vi.fn()}
onDensityChange={vi.fn()}
/>
);

expect(screen.getByLabelText("Loading events")).toHaveAttribute("aria-busy", "true");
});

it("renders filtered empty state and clear action", () => {
const columns = {
status: true,
time: true,
description: true,
contract: true,
actions: true,
};
const onClearSearch = vi.fn();

render(
<EventFeedTable
events={[]}
emptyStateCause="filtered"
columns={columns}
density="comfortable"
onToggleColumn={vi.fn()}
onDensityChange={vi.fn()}
onClearSearch={onClearSearch}
/>
);

expect(screen.getByText("No events match your search.")).toBeInTheDocument();
fireEvent.click(screen.getByRole("button", { name: "Clear search" }));
expect(onClearSearch).toHaveBeenCalledTimes(1);
});

it("renders connection error empty state", () => {
const columns = {
status: true,
time: true,
description: true,
contract: true,
actions: true,
};

render(
<EventFeedTable
events={[]}
emptyStateCause="connection-error"
columns={columns}
density="comfortable"
onToggleColumn={vi.fn()}
onDensityChange={vi.fn()}
/>
);

expect(screen.getByText("Could not connect to Stellar. Retrying...")).toBeInTheDocument();
});
});
Loading